From 8288635071a972265af0dd2aa547f8376185f458 Mon Sep 17 00:00:00 2001 From: Rasmus Dahlberg Date: Thu, 1 Apr 2021 00:17:06 +0200 Subject: added drafty ascii charts (work in progress) --- doc/formats.md | 160 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 160 insertions(+) create mode 100644 doc/formats.md diff --git a/doc/formats.md b/doc/formats.md new file mode 100644 index 0000000..bffd05f --- /dev/null +++ b/doc/formats.md @@ -0,0 +1,160 @@ +# Formats +This document defines data structures and data formats. + +## Overview +Here we give an overview of our presentation language / serialization rules. + +All integers are represented by 64-bit unsigned integers in network byte order. + +Variable length lists have an integer specifying its length. Then each list +item is enumerated. + +TODO: fixme. + +## Items +Every item type start with a versioned format specifier. Protocol version 1 +uses format specifiers in the range 1--X. + +### Request data structures +Log endpoints that take input data use the following request data structures. + +#### `get_entries_v1` +``` +0 Format 8 16 24 ++----------+----------------+----------------+ +| 1 | Start Size | End Size | ++----------+----------------+----------------+ + uint64 uint64 uint64 +``` +- Format is always 1 for items of type `get_entries_v1`. +- Start size specifies the index of the first Merkle tree leaf to retrieve. +- End size specifies the index of the last Merkle tree leaf to retrieve. + +#### `get_proof_by_hash_v1` +``` +0 Format 8 16 48 ++----------+----------------+----------------+ +| 2 | Tree size | Leaf hash | ++----------+----------------+----------------+ + uint64 uint64 fixed byte array +``` +- Format is always 2 for items of type `get_proof_by_hash_v1`. +- Leaf hash is computed as described in [RFC 6962/bis, §2.1.1](https://tools.ietf.org/html/draft-ietf-trans-rfc6962-bis-35#section-2.1.1). +- Tree size specifies which Merkle tree root inclusion should be proven for. + +#### `get_consistency_proof_v1` +``` +0 Format 8 16 24 ++----------+----------------+----------------+ +| 3 | Old size | New size | ++----------+----------------+----------------+ + uint64 uint64 uint64 +``` +- Format is always 3 for items of type `get_consistency_proof_v1`. +- Old size specifies the tree size of an older Merkle tree head. +- New size specifies the tree size of a newer Merkle tree head. + +### Proof and log data structures +#### `inclusion_proof_v1` +``` + --zero or more node hashes--> +0 Format 8 48 56 64 72 72+Length ++----------+----------------+----------------+----------------+----------------+--------//--------+ +| 4 | Identifier | Tree size | Leaf index | Length | Node hashes | ++----------+----------------+----------------+----------------+----------------+--------//--------+ + uint64 ed25519_v1 uint64 uint64 uint64 list body +``` +- Format is always 4 for items of type `inclusion_proof_v1`. +- Identifier identifies the log uniquely as an `ed25519_v1` item. +- Tree size is the size of the Merkle tree that the proof is based on. +- Leaf index is a zero-based index of the log entry that the proof is based on. +- The remaining part is a list of node hashes. + - Length specifies the full byte size of the list. It must be `32 * m`, + where `m >= 0`. This means that an inclusion needs zero or more node + hashes to be well-formed. + - Node hash is a node hash in the Merkle tree that the proof is based on. + +Remark: the list of node hashes is generated and verified as in [RFC 6962/bis, +§2.1.3](https://tools.ietf.org/html/draft-ietf-trans-rfc6962-bis-35#section-2.1.3). + +#### `consistency_proof_v1` +``` + --zero or more node hashes--> +0 Format 8 48 56 64 72 72+Length ++----------+----------------+----------------+----------------+----------------+--------//--------+ +| 5 | Identifier | Old size | New size | Length | Node hashes | ++----------+----------------+----------------+----------------+----------------+--------//--------+ + uint64 ed25519_v1 uint64 uint64 uint64 list body +``` +- Format is always 5 for items of type `consistency_proof_v1`. +- Identifier identifies the log uniquely as an `ed25519_v1` item. +- Old size is the tree size of the older Merkle tree. +- New size is the tree size of the newer Merkle tree. +- The remaining part is a list of node hashes. + - Length specifies the full byte size of the list. It must be `32 * m`, + where `m >= 0`. This means that a consistenty proof needs zero or more node + hashes to be well-formed. + - Node hash is a node hash from the older or the newer Merkle tree. + +Remark: the list of node hashes is generated and verified as in [RFC 6962/bis, +§2.1.4](https://tools.ietf.org/html/draft-ietf-trans-rfc6962-bis-35#section-2.1.4). + +#### `signed_tree_head_v1` +``` + ----one or more signature-identifier pairs-------> +0 Format 8 16 24 56 64 128 168 64+Length ++----------+----------------+----------------+----------------+----------------+----------------+----------------+--//--+ +| 6 | Timestamp | Tree size | Root hash | Length | Signature | Identifier | .... | ++----------+----------------+----------------+----------------+----------------+----------------+----------------+--//--+ + uint64 uint64 uint64 fixed byte array uint64 fixed byte array ed25519_v1 cont. list body +``` +- Format is always 6 for items of type `signed_tree_head_v1`. +- Timestamp is the time since the UNIX epoch (January 1, 1970 00:00:00 UTC) in +milliseconds. +- Tree size is the number of leaves in the current Merkle tree. +- Root hash is the root hash of the current Merkle tree. +- The remaining part is a list of signature-identifier pairs. + - Length specifies the full byte size of the list. It must be `104 * m`, + where `m > 1`. This means that a signed tree head needs at least one + signature-identifier pair to be well-formed. + - Signature is an Ed25519 signature over bytes 0--56. The signature is + encodes as in [RFC 8032, §3.3](https://tools.ietf.org/html/rfc8032#section-3.3). + - Identifier identifies the signer uniquely as an `ed25519_v1` item. + +Remark: there may be multiple signature-identifier pairs if the log is cosigned. + +#### `signed_checksum32_ed25519_v1` +``` +0 Format 8 40 56 56+Length 120+Length 160+Length ++----------+----------------+----------------+-------//---------+----------------+--------//--------+ +| 7 | Checksum | Length | Identifier | Signature | Namespace | ++----------+----------------+----------------+-------//---------+----------------+--------//--------+ + uint64 fixed byte array uint64 byte array fixed byte array ed25519_v1 +``` +- Format is always 7 for items of type `signed_checksum32_ed25519_v1`. +- Checksum is a 32-byte checksum that represents a data item of opaque type. +- Length specified the full byte size of the following identifier. It must be +larger than zero and less than 128. +- Identifier identifies what the checksum represents. The aforementioned length +constraint means that the identifier cannot be omitted or exceed 128 bytes. +- Signature is an Ed25519 signature over bytes 0--56+Length. The signature is +encodes as in [RFC 8032, §3.3](https://tools.ietf.org/html/rfc8032#section-3.3). +- Namespace is an `ed25519_v1` item that identifies the signer uniquely. + +Remark: to keep this checksum entry as simple as possible it does not have a +variable length checksum or any agility with regards to the signing namespace. +This means that we need to have multiple leaf types that follow the pattern +`signed_checksum{32,64}_namespace_v1`. + +### Namespace data structures +#### `ed25519_v1` +``` +0 Format 8 40 ++----------+----------------+ +| 8 | public key | ++----------+----------------+ + uint64 fixed byte array +``` +- The format is always 8 for items of type `ed25519_v1`. +- The public Ed25519 verification key is always 32 bytes. See encoding in [RFC +8032, §3.2](https://tools.ietf.org/html/rfc8032#section-3.2). -- cgit v1.2.3 From d4337d27e4c4d1bf58bde6a2e34f860d70f0ca5f Mon Sep 17 00:00:00 2001 From: Linus Nordberg Date: Thu, 1 Apr 2021 10:56:19 +0200 Subject: express doc/format.md in trunnel --- trunnel/README.md | 9 + trunnel/stfe.c | 2796 ++++++++++++++++++++++++++++++++++++++++++++++++++ trunnel/stfe.h | 985 ++++++++++++++++++ trunnel/stfe.trunnel | 85 ++ 4 files changed, 3875 insertions(+) create mode 100644 trunnel/README.md create mode 100644 trunnel/stfe.c create mode 100644 trunnel/stfe.h create mode 100644 trunnel/stfe.trunnel diff --git a/trunnel/README.md b/trunnel/README.md new file mode 100644 index 0000000..74180a0 --- /dev/null +++ b/trunnel/README.md @@ -0,0 +1,9 @@ +Expressing doc/formats.md in trunnel[0]. + +- stfe.trunnel is the input file +- stfe.[ch] are output files from running trunnel on stfe.trunnel. + +TODO: Generate go code using [1]. + +[0] https://gitweb.torproject.org/trunnel.git +[1] https://github.com/mmcloughlin/trunnel diff --git a/trunnel/stfe.c b/trunnel/stfe.c new file mode 100644 index 0000000..58ecd66 --- /dev/null +++ b/trunnel/stfe.c @@ -0,0 +1,2796 @@ +/* stfe.c -- generated by Trunnel v1.5.3. + * https://gitweb.torproject.org/trunnel.git + * You probably shouldn't edit this file. + */ +#include +#include "trunnel-impl.h" + +#include "stfe.h" + +#define TRUNNEL_SET_ERROR_CODE(obj) \ + do { \ + (obj)->trunnel_error_code_ = 1; \ + } while (0) + +#if defined(__COVERITY__) || defined(__clang_analyzer__) +/* If we're running a static analysis tool, we don't want it to complain + * that some of our remaining-bytes checks are dead-code. */ +int stfe_deadcode_dummy__ = 0; +#define OR_DEADCODE_DUMMY || stfe_deadcode_dummy__ +#else +#define OR_DEADCODE_DUMMY +#endif + +#define CHECK_REMAINING(nbytes, label) \ + do { \ + if (remaining < (nbytes) OR_DEADCODE_DUMMY) { \ + goto label; \ + } \ + } while (0) + +ed25519_v1_t * +ed25519_v1_new(void) +{ + ed25519_v1_t *val = trunnel_calloc(1, sizeof(ed25519_v1_t)); + if (NULL == val) + return NULL; + val->format = T_ED25519_V1; + return val; +} + +/** Release all storage held inside 'obj', but do not free 'obj'. + */ +static void +ed25519_v1_clear(ed25519_v1_t *obj) +{ + (void) obj; +} + +void +ed25519_v1_free(ed25519_v1_t *obj) +{ + if (obj == NULL) + return; + ed25519_v1_clear(obj); + trunnel_memwipe(obj, sizeof(ed25519_v1_t)); + trunnel_free_(obj); +} + +uint64_t +ed25519_v1_get_format(const ed25519_v1_t *inp) +{ + return inp->format; +} +int +ed25519_v1_set_format(ed25519_v1_t *inp, uint64_t val) +{ + if (! ((val == T_ED25519_V1))) { + TRUNNEL_SET_ERROR_CODE(inp); + return -1; + } + inp->format = val; + return 0; +} +size_t +ed25519_v1_getlen_pubkey(const ed25519_v1_t *inp) +{ + (void)inp; return 32; +} + +uint8_t +ed25519_v1_get_pubkey(ed25519_v1_t *inp, size_t idx) +{ + trunnel_assert(idx < 32); + return inp->pubkey[idx]; +} + +uint8_t +ed25519_v1_getconst_pubkey(const ed25519_v1_t *inp, size_t idx) +{ + return ed25519_v1_get_pubkey((ed25519_v1_t*)inp, idx); +} +int +ed25519_v1_set_pubkey(ed25519_v1_t *inp, size_t idx, uint8_t elt) +{ + trunnel_assert(idx < 32); + inp->pubkey[idx] = elt; + return 0; +} + +uint8_t * +ed25519_v1_getarray_pubkey(ed25519_v1_t *inp) +{ + return inp->pubkey; +} +const uint8_t * +ed25519_v1_getconstarray_pubkey(const ed25519_v1_t *inp) +{ + return (const uint8_t *)ed25519_v1_getarray_pubkey((ed25519_v1_t*)inp); +} +const char * +ed25519_v1_check(const ed25519_v1_t *obj) +{ + if (obj == NULL) + return "Object was NULL"; + if (obj->trunnel_error_code_) + return "A set function failed on this object"; + if (! (obj->format == T_ED25519_V1)) + return "Integer out of bounds"; + return NULL; +} + +ssize_t +ed25519_v1_encoded_len(const ed25519_v1_t *obj) +{ + ssize_t result = 0; + + if (NULL != ed25519_v1_check(obj)) + return -1; + + + /* Length of u64 format IN [T_ED25519_V1] */ + result += 8; + + /* Length of u8 pubkey[32] */ + result += 32; + return result; +} +int +ed25519_v1_clear_errors(ed25519_v1_t *obj) +{ + int r = obj->trunnel_error_code_; + obj->trunnel_error_code_ = 0; + return r; +} +ssize_t +ed25519_v1_encode(uint8_t *output, const size_t avail, const ed25519_v1_t *obj) +{ + ssize_t result = 0; + size_t written = 0; + uint8_t *ptr = output; + const char *msg; +#ifdef TRUNNEL_CHECK_ENCODED_LEN + const ssize_t encoded_len = ed25519_v1_encoded_len(obj); +#endif + + if (NULL != (msg = ed25519_v1_check(obj))) + goto check_failed; + +#ifdef TRUNNEL_CHECK_ENCODED_LEN + trunnel_assert(encoded_len >= 0); +#endif + + /* Encode u64 format IN [T_ED25519_V1] */ + trunnel_assert(written <= avail); + if (avail - written < 8) + goto truncated; + trunnel_set_uint64(ptr, trunnel_htonll(obj->format)); + written += 8; ptr += 8; + + /* Encode u8 pubkey[32] */ + trunnel_assert(written <= avail); + if (avail - written < 32) + goto truncated; + memcpy(ptr, obj->pubkey, 32); + written += 32; ptr += 32; + + + trunnel_assert(ptr == output + written); +#ifdef TRUNNEL_CHECK_ENCODED_LEN + { + trunnel_assert(encoded_len >= 0); + trunnel_assert((size_t)encoded_len == written); + } + +#endif + + return written; + + truncated: + result = -2; + goto fail; + check_failed: + (void)msg; + result = -1; + goto fail; + fail: + trunnel_assert(result < 0); + return result; +} + +/** As ed25519_v1_parse(), but do not allocate the output object. + */ +static ssize_t +ed25519_v1_parse_into(ed25519_v1_t *obj, const uint8_t *input, const size_t len_in) +{ + const uint8_t *ptr = input; + size_t remaining = len_in; + ssize_t result = 0; + (void)result; + + /* Parse u64 format IN [T_ED25519_V1] */ + CHECK_REMAINING(8, truncated); + obj->format = trunnel_ntohll(trunnel_get_uint64(ptr)); + remaining -= 8; ptr += 8; + if (! (obj->format == T_ED25519_V1)) + goto fail; + + /* Parse u8 pubkey[32] */ + CHECK_REMAINING(32, truncated); + memcpy(obj->pubkey, ptr, 32); + remaining -= 32; ptr += 32; + trunnel_assert(ptr + remaining == input + len_in); + return len_in - remaining; + + truncated: + return -2; + fail: + result = -1; + return result; +} + +ssize_t +ed25519_v1_parse(ed25519_v1_t **output, const uint8_t *input, const size_t len_in) +{ + ssize_t result; + *output = ed25519_v1_new(); + if (NULL == *output) + return -1; + result = ed25519_v1_parse_into(*output, input, len_in); + if (result < 0) { + ed25519_v1_free(*output); + *output = NULL; + } + return result; +} +req_get_consistency_proof_v1_t * +req_get_consistency_proof_v1_new(void) +{ + req_get_consistency_proof_v1_t *val = trunnel_calloc(1, sizeof(req_get_consistency_proof_v1_t)); + if (NULL == val) + return NULL; + return val; +} + +/** Release all storage held inside 'obj', but do not free 'obj'. + */ +static void +req_get_consistency_proof_v1_clear(req_get_consistency_proof_v1_t *obj) +{ + (void) obj; +} + +void +req_get_consistency_proof_v1_free(req_get_consistency_proof_v1_t *obj) +{ + if (obj == NULL) + return; + req_get_consistency_proof_v1_clear(obj); + trunnel_memwipe(obj, sizeof(req_get_consistency_proof_v1_t)); + trunnel_free_(obj); +} + +uint64_t +req_get_consistency_proof_v1_get_old_size(const req_get_consistency_proof_v1_t *inp) +{ + return inp->old_size; +} +int +req_get_consistency_proof_v1_set_old_size(req_get_consistency_proof_v1_t *inp, uint64_t val) +{ + inp->old_size = val; + return 0; +} +uint64_t +req_get_consistency_proof_v1_get_new_size(const req_get_consistency_proof_v1_t *inp) +{ + return inp->new_size; +} +int +req_get_consistency_proof_v1_set_new_size(req_get_consistency_proof_v1_t *inp, uint64_t val) +{ + inp->new_size = val; + return 0; +} +const char * +req_get_consistency_proof_v1_check(const req_get_consistency_proof_v1_t *obj) +{ + if (obj == NULL) + return "Object was NULL"; + if (obj->trunnel_error_code_) + return "A set function failed on this object"; + return NULL; +} + +ssize_t +req_get_consistency_proof_v1_encoded_len(const req_get_consistency_proof_v1_t *obj) +{ + ssize_t result = 0; + + if (NULL != req_get_consistency_proof_v1_check(obj)) + return -1; + + + /* Length of u64 old_size */ + result += 8; + + /* Length of u64 new_size */ + result += 8; + return result; +} +int +req_get_consistency_proof_v1_clear_errors(req_get_consistency_proof_v1_t *obj) +{ + int r = obj->trunnel_error_code_; + obj->trunnel_error_code_ = 0; + return r; +} +ssize_t +req_get_consistency_proof_v1_encode(uint8_t *output, const size_t avail, const req_get_consistency_proof_v1_t *obj) +{ + ssize_t result = 0; + size_t written = 0; + uint8_t *ptr = output; + const char *msg; +#ifdef TRUNNEL_CHECK_ENCODED_LEN + const ssize_t encoded_len = req_get_consistency_proof_v1_encoded_len(obj); +#endif + + if (NULL != (msg = req_get_consistency_proof_v1_check(obj))) + goto check_failed; + +#ifdef TRUNNEL_CHECK_ENCODED_LEN + trunnel_assert(encoded_len >= 0); +#endif + + /* Encode u64 old_size */ + trunnel_assert(written <= avail); + if (avail - written < 8) + goto truncated; + trunnel_set_uint64(ptr, trunnel_htonll(obj->old_size)); + written += 8; ptr += 8; + + /* Encode u64 new_size */ + trunnel_assert(written <= avail); + if (avail - written < 8) + goto truncated; + trunnel_set_uint64(ptr, trunnel_htonll(obj->new_size)); + written += 8; ptr += 8; + + + trunnel_assert(ptr == output + written); +#ifdef TRUNNEL_CHECK_ENCODED_LEN + { + trunnel_assert(encoded_len >= 0); + trunnel_assert((size_t)encoded_len == written); + } + +#endif + + return written; + + truncated: + result = -2; + goto fail; + check_failed: + (void)msg; + result = -1; + goto fail; + fail: + trunnel_assert(result < 0); + return result; +} + +/** As req_get_consistency_proof_v1_parse(), but do not allocate the + * output object. + */ +static ssize_t +req_get_consistency_proof_v1_parse_into(req_get_consistency_proof_v1_t *obj, const uint8_t *input, const size_t len_in) +{ + const uint8_t *ptr = input; + size_t remaining = len_in; + ssize_t result = 0; + (void)result; + + /* Parse u64 old_size */ + CHECK_REMAINING(8, truncated); + obj->old_size = trunnel_ntohll(trunnel_get_uint64(ptr)); + remaining -= 8; ptr += 8; + + /* Parse u64 new_size */ + CHECK_REMAINING(8, truncated); + obj->new_size = trunnel_ntohll(trunnel_get_uint64(ptr)); + remaining -= 8; ptr += 8; + trunnel_assert(ptr + remaining == input + len_in); + return len_in - remaining; + + truncated: + return -2; +} + +ssize_t +req_get_consistency_proof_v1_parse(req_get_consistency_proof_v1_t **output, const uint8_t *input, const size_t len_in) +{ + ssize_t result; + *output = req_get_consistency_proof_v1_new(); + if (NULL == *output) + return -1; + result = req_get_consistency_proof_v1_parse_into(*output, input, len_in); + if (result < 0) { + req_get_consistency_proof_v1_free(*output); + *output = NULL; + } + return result; +} +req_get_entries_v1_t * +req_get_entries_v1_new(void) +{ + req_get_entries_v1_t *val = trunnel_calloc(1, sizeof(req_get_entries_v1_t)); + if (NULL == val) + return NULL; + return val; +} + +/** Release all storage held inside 'obj', but do not free 'obj'. + */ +static void +req_get_entries_v1_clear(req_get_entries_v1_t *obj) +{ + (void) obj; +} + +void +req_get_entries_v1_free(req_get_entries_v1_t *obj) +{ + if (obj == NULL) + return; + req_get_entries_v1_clear(obj); + trunnel_memwipe(obj, sizeof(req_get_entries_v1_t)); + trunnel_free_(obj); +} + +uint64_t +req_get_entries_v1_get_start_size(const req_get_entries_v1_t *inp) +{ + return inp->start_size; +} +int +req_get_entries_v1_set_start_size(req_get_entries_v1_t *inp, uint64_t val) +{ + inp->start_size = val; + return 0; +} +uint64_t +req_get_entries_v1_get_end_size(const req_get_entries_v1_t *inp) +{ + return inp->end_size; +} +int +req_get_entries_v1_set_end_size(req_get_entries_v1_t *inp, uint64_t val) +{ + inp->end_size = val; + return 0; +} +const char * +req_get_entries_v1_check(const req_get_entries_v1_t *obj) +{ + if (obj == NULL) + return "Object was NULL"; + if (obj->trunnel_error_code_) + return "A set function failed on this object"; + return NULL; +} + +ssize_t +req_get_entries_v1_encoded_len(const req_get_entries_v1_t *obj) +{ + ssize_t result = 0; + + if (NULL != req_get_entries_v1_check(obj)) + return -1; + + + /* Length of u64 start_size */ + result += 8; + + /* Length of u64 end_size */ + result += 8; + return result; +} +int +req_get_entries_v1_clear_errors(req_get_entries_v1_t *obj) +{ + int r = obj->trunnel_error_code_; + obj->trunnel_error_code_ = 0; + return r; +} +ssize_t +req_get_entries_v1_encode(uint8_t *output, const size_t avail, const req_get_entries_v1_t *obj) +{ + ssize_t result = 0; + size_t written = 0; + uint8_t *ptr = output; + const char *msg; +#ifdef TRUNNEL_CHECK_ENCODED_LEN + const ssize_t encoded_len = req_get_entries_v1_encoded_len(obj); +#endif + + if (NULL != (msg = req_get_entries_v1_check(obj))) + goto check_failed; + +#ifdef TRUNNEL_CHECK_ENCODED_LEN + trunnel_assert(encoded_len >= 0); +#endif + + /* Encode u64 start_size */ + trunnel_assert(written <= avail); + if (avail - written < 8) + goto truncated; + trunnel_set_uint64(ptr, trunnel_htonll(obj->start_size)); + written += 8; ptr += 8; + + /* Encode u64 end_size */ + trunnel_assert(written <= avail); + if (avail - written < 8) + goto truncated; + trunnel_set_uint64(ptr, trunnel_htonll(obj->end_size)); + written += 8; ptr += 8; + + + trunnel_assert(ptr == output + written); +#ifdef TRUNNEL_CHECK_ENCODED_LEN + { + trunnel_assert(encoded_len >= 0); + trunnel_assert((size_t)encoded_len == written); + } + +#endif + + return written; + + truncated: + result = -2; + goto fail; + check_failed: + (void)msg; + result = -1; + goto fail; + fail: + trunnel_assert(result < 0); + return result; +} + +/** As req_get_entries_v1_parse(), but do not allocate the output + * object. + */ +static ssize_t +req_get_entries_v1_parse_into(req_get_entries_v1_t *obj, const uint8_t *input, const size_t len_in) +{ + const uint8_t *ptr = input; + size_t remaining = len_in; + ssize_t result = 0; + (void)result; + + /* Parse u64 start_size */ + CHECK_REMAINING(8, truncated); + obj->start_size = trunnel_ntohll(trunnel_get_uint64(ptr)); + remaining -= 8; ptr += 8; + + /* Parse u64 end_size */ + CHECK_REMAINING(8, truncated); + obj->end_size = trunnel_ntohll(trunnel_get_uint64(ptr)); + remaining -= 8; ptr += 8; + trunnel_assert(ptr + remaining == input + len_in); + return len_in - remaining; + + truncated: + return -2; +} + +ssize_t +req_get_entries_v1_parse(req_get_entries_v1_t **output, const uint8_t *input, const size_t len_in) +{ + ssize_t result; + *output = req_get_entries_v1_new(); + if (NULL == *output) + return -1; + result = req_get_entries_v1_parse_into(*output, input, len_in); + if (result < 0) { + req_get_entries_v1_free(*output); + *output = NULL; + } + return result; +} +req_get_proof_by_hash_v1_t * +req_get_proof_by_hash_v1_new(void) +{ + req_get_proof_by_hash_v1_t *val = trunnel_calloc(1, sizeof(req_get_proof_by_hash_v1_t)); + if (NULL == val) + return NULL; + return val; +} + +/** Release all storage held inside 'obj', but do not free 'obj'. + */ +static void +req_get_proof_by_hash_v1_clear(req_get_proof_by_hash_v1_t *obj) +{ + (void) obj; +} + +void +req_get_proof_by_hash_v1_free(req_get_proof_by_hash_v1_t *obj) +{ + if (obj == NULL) + return; + req_get_proof_by_hash_v1_clear(obj); + trunnel_memwipe(obj, sizeof(req_get_proof_by_hash_v1_t)); + trunnel_free_(obj); +} + +uint64_t +req_get_proof_by_hash_v1_get_tree_size(const req_get_proof_by_hash_v1_t *inp) +{ + return inp->tree_size; +} +int +req_get_proof_by_hash_v1_set_tree_size(req_get_proof_by_hash_v1_t *inp, uint64_t val) +{ + inp->tree_size = val; + return 0; +} +size_t +req_get_proof_by_hash_v1_getlen_leaf_hash(const req_get_proof_by_hash_v1_t *inp) +{ + (void)inp; return 32; +} + +uint8_t +req_get_proof_by_hash_v1_get_leaf_hash(req_get_proof_by_hash_v1_t *inp, size_t idx) +{ + trunnel_assert(idx < 32); + return inp->leaf_hash[idx]; +} + +uint8_t +req_get_proof_by_hash_v1_getconst_leaf_hash(const req_get_proof_by_hash_v1_t *inp, size_t idx) +{ + return req_get_proof_by_hash_v1_get_leaf_hash((req_get_proof_by_hash_v1_t*)inp, idx); +} +int +req_get_proof_by_hash_v1_set_leaf_hash(req_get_proof_by_hash_v1_t *inp, size_t idx, uint8_t elt) +{ + trunnel_assert(idx < 32); + inp->leaf_hash[idx] = elt; + return 0; +} + +uint8_t * +req_get_proof_by_hash_v1_getarray_leaf_hash(req_get_proof_by_hash_v1_t *inp) +{ + return inp->leaf_hash; +} +const uint8_t * +req_get_proof_by_hash_v1_getconstarray_leaf_hash(const req_get_proof_by_hash_v1_t *inp) +{ + return (const uint8_t *)req_get_proof_by_hash_v1_getarray_leaf_hash((req_get_proof_by_hash_v1_t*)inp); +} +const char * +req_get_proof_by_hash_v1_check(const req_get_proof_by_hash_v1_t *obj) +{ + if (obj == NULL) + return "Object was NULL"; + if (obj->trunnel_error_code_) + return "A set function failed on this object"; + return NULL; +} + +ssize_t +req_get_proof_by_hash_v1_encoded_len(const req_get_proof_by_hash_v1_t *obj) +{ + ssize_t result = 0; + + if (NULL != req_get_proof_by_hash_v1_check(obj)) + return -1; + + + /* Length of u64 tree_size */ + result += 8; + + /* Length of u8 leaf_hash[32] */ + result += 32; + return result; +} +int +req_get_proof_by_hash_v1_clear_errors(req_get_proof_by_hash_v1_t *obj) +{ + int r = obj->trunnel_error_code_; + obj->trunnel_error_code_ = 0; + return r; +} +ssize_t +req_get_proof_by_hash_v1_encode(uint8_t *output, const size_t avail, const req_get_proof_by_hash_v1_t *obj) +{ + ssize_t result = 0; + size_t written = 0; + uint8_t *ptr = output; + const char *msg; +#ifdef TRUNNEL_CHECK_ENCODED_LEN + const ssize_t encoded_len = req_get_proof_by_hash_v1_encoded_len(obj); +#endif + + if (NULL != (msg = req_get_proof_by_hash_v1_check(obj))) + goto check_failed; + +#ifdef TRUNNEL_CHECK_ENCODED_LEN + trunnel_assert(encoded_len >= 0); +#endif + + /* Encode u64 tree_size */ + trunnel_assert(written <= avail); + if (avail - written < 8) + goto truncated; + trunnel_set_uint64(ptr, trunnel_htonll(obj->tree_size)); + written += 8; ptr += 8; + + /* Encode u8 leaf_hash[32] */ + trunnel_assert(written <= avail); + if (avail - written < 32) + goto truncated; + memcpy(ptr, obj->leaf_hash, 32); + written += 32; ptr += 32; + + + trunnel_assert(ptr == output + written); +#ifdef TRUNNEL_CHECK_ENCODED_LEN + { + trunnel_assert(encoded_len >= 0); + trunnel_assert((size_t)encoded_len == written); + } + +#endif + + return written; + + truncated: + result = -2; + goto fail; + check_failed: + (void)msg; + result = -1; + goto fail; + fail: + trunnel_assert(result < 0); + return result; +} + +/** As req_get_proof_by_hash_v1_parse(), but do not allocate the + * output object. + */ +static ssize_t +req_get_proof_by_hash_v1_parse_into(req_get_proof_by_hash_v1_t *obj, const uint8_t *input, const size_t len_in) +{ + const uint8_t *ptr = input; + size_t remaining = len_in; + ssize_t result = 0; + (void)result; + + /* Parse u64 tree_size */ + CHECK_REMAINING(8, truncated); + obj->tree_size = trunnel_ntohll(trunnel_get_uint64(ptr)); + remaining -= 8; ptr += 8; + + /* Parse u8 leaf_hash[32] */ + CHECK_REMAINING(32, truncated); + memcpy(obj->leaf_hash, ptr, 32); + remaining -= 32; ptr += 32; + trunnel_assert(ptr + remaining == input + len_in); + return len_in - remaining; + + truncated: + return -2; +} + +ssize_t +req_get_proof_by_hash_v1_parse(req_get_proof_by_hash_v1_t **output, const uint8_t *input, const size_t len_in) +{ + ssize_t result; + *output = req_get_proof_by_hash_v1_new(); + if (NULL == *output) + return -1; + result = req_get_proof_by_hash_v1_parse_into(*output, input, len_in); + if (result < 0) { + req_get_proof_by_hash_v1_free(*output); + *output = NULL; + } + return result; +} +signed_tree_head_v1_t * +signed_tree_head_v1_new(void) +{ + signed_tree_head_v1_t *val = trunnel_calloc(1, sizeof(signed_tree_head_v1_t)); + if (NULL == val) + return NULL; + val->format = T_SIGNED_TREE_HEAD_V1; + return val; +} + +/** Release all storage held inside 'obj', but do not free 'obj'. + */ +static void +signed_tree_head_v1_clear(signed_tree_head_v1_t *obj) +{ + (void) obj; + TRUNNEL_DYNARRAY_WIPE(&obj->sigident); + TRUNNEL_DYNARRAY_CLEAR(&obj->sigident); +} + +void +signed_tree_head_v1_free(signed_tree_head_v1_t *obj) +{ + if (obj == NULL) + return; + signed_tree_head_v1_clear(obj); + trunnel_memwipe(obj, sizeof(signed_tree_head_v1_t)); + trunnel_free_(obj); +} + +uint64_t +signed_tree_head_v1_get_format(const signed_tree_head_v1_t *inp) +{ + return inp->format; +} +int +signed_tree_head_v1_set_format(signed_tree_head_v1_t *inp, uint64_t val) +{ + if (! ((val == T_SIGNED_TREE_HEAD_V1))) { + TRUNNEL_SET_ERROR_CODE(inp); + return -1; + } + inp->format = val; + return 0; +} +uint64_t +signed_tree_head_v1_get_timestamp(const signed_tree_head_v1_t *inp) +{ + return inp->timestamp; +} +int +signed_tree_head_v1_set_timestamp(signed_tree_head_v1_t *inp, uint64_t val) +{ + inp->timestamp = val; + return 0; +} +uint64_t +signed_tree_head_v1_get_tree_size(const signed_tree_head_v1_t *inp) +{ + return inp->tree_size; +} +int +signed_tree_head_v1_set_tree_size(signed_tree_head_v1_t *inp, uint64_t val) +{ + inp->tree_size = val; + return 0; +} +size_t +signed_tree_head_v1_getlen_root_hash(const signed_tree_head_v1_t *inp) +{ + (void)inp; return 32; +} + +uint8_t +signed_tree_head_v1_get_root_hash(signed_tree_head_v1_t *inp, size_t idx) +{ + trunnel_assert(idx < 32); + return inp->root_hash[idx]; +} + +uint8_t +signed_tree_head_v1_getconst_root_hash(const signed_tree_head_v1_t *inp, size_t idx) +{ + return signed_tree_head_v1_get_root_hash((signed_tree_head_v1_t*)inp, idx); +} +int +signed_tree_head_v1_set_root_hash(signed_tree_head_v1_t *inp, size_t idx, uint8_t elt) +{ + trunnel_assert(idx < 32); + inp->root_hash[idx] = elt; + return 0; +} + +uint8_t * +signed_tree_head_v1_getarray_root_hash(signed_tree_head_v1_t *inp) +{ + return inp->root_hash; +} +const uint8_t * +signed_tree_head_v1_getconstarray_root_hash(const signed_tree_head_v1_t *inp) +{ + return (const uint8_t *)signed_tree_head_v1_getarray_root_hash((signed_tree_head_v1_t*)inp); +} +uint64_t +signed_tree_head_v1_get_length(const signed_tree_head_v1_t *inp) +{ + return inp->length; +} +int +signed_tree_head_v1_set_length(signed_tree_head_v1_t *inp, uint64_t val) +{ + inp->length = val; + return 0; +} +size_t +signed_tree_head_v1_getlen_sigident(const signed_tree_head_v1_t *inp) +{ + return TRUNNEL_DYNARRAY_LEN(&inp->sigident); +} + +uint8_t +signed_tree_head_v1_get_sigident(signed_tree_head_v1_t *inp, size_t idx) +{ + return TRUNNEL_DYNARRAY_GET(&inp->sigident, idx); +} + +uint8_t +signed_tree_head_v1_getconst_sigident(const signed_tree_head_v1_t *inp, size_t idx) +{ + return signed_tree_head_v1_get_sigident((signed_tree_head_v1_t*)inp, idx); +} +int +signed_tree_head_v1_set_sigident(signed_tree_head_v1_t *inp, size_t idx, uint8_t elt) +{ + TRUNNEL_DYNARRAY_SET(&inp->sigident, idx, elt); + return 0; +} +int +signed_tree_head_v1_add_sigident(signed_tree_head_v1_t *inp, uint8_t elt) +{ +#if SIZE_MAX >= UINT64_MAX + if (inp->sigident.n_ == UINT64_MAX) + goto trunnel_alloc_failed; +#endif + TRUNNEL_DYNARRAY_ADD(uint8_t, &inp->sigident, elt, {}); + return 0; + trunnel_alloc_failed: + TRUNNEL_SET_ERROR_CODE(inp); + return -1; +} + +uint8_t * +signed_tree_head_v1_getarray_sigident(signed_tree_head_v1_t *inp) +{ + return inp->sigident.elts_; +} +const uint8_t * +signed_tree_head_v1_getconstarray_sigident(const signed_tree_head_v1_t *inp) +{ + return (const uint8_t *)signed_tree_head_v1_getarray_sigident((signed_tree_head_v1_t*)inp); +} +int +signed_tree_head_v1_setlen_sigident(signed_tree_head_v1_t *inp, size_t newlen) +{ + uint8_t *newptr; +#if UINT64_MAX < SIZE_MAX + if (newlen > UINT64_MAX) + goto trunnel_alloc_failed; +#endif + newptr = trunnel_dynarray_setlen(&inp->sigident.allocated_, + &inp->sigident.n_, inp->sigident.elts_, newlen, + sizeof(inp->sigident.elts_[0]), (trunnel_free_fn_t) NULL, + &inp->trunnel_error_code_); + if (newlen != 0 && newptr == NULL) + goto trunnel_alloc_failed; + inp->sigident.elts_ = newptr; + return 0; + trunnel_alloc_failed: + TRUNNEL_SET_ERROR_CODE(inp); + return -1; +} +const char * +signed_tree_head_v1_check(const signed_tree_head_v1_t *obj) +{ + if (obj == NULL) + return "Object was NULL"; + if (obj->trunnel_error_code_) + return "A set function failed on this object"; + if (! (obj->format == T_SIGNED_TREE_HEAD_V1)) + return "Integer out of bounds"; + if (TRUNNEL_DYNARRAY_LEN(&obj->sigident) != obj->length) + return "Length mismatch for sigident"; + return NULL; +} + +ssize_t +signed_tree_head_v1_encoded_len(const signed_tree_head_v1_t *obj) +{ + ssize_t result = 0; + + if (NULL != signed_tree_head_v1_check(obj)) + return -1; + + + /* Length of u64 format IN [T_SIGNED_TREE_HEAD_V1] */ + result += 8; + + /* Length of u64 timestamp */ + result += 8; + + /* Length of u64 tree_size */ + result += 8; + + /* Length of u8 root_hash[32] */ + result += 32; + + /* Length of u64 length */ + result += 8; + + /* Length of u8 sigident[length] */ + result += TRUNNEL_DYNARRAY_LEN(&obj->sigident); + return result; +} +int +signed_tree_head_v1_clear_errors(signed_tree_head_v1_t *obj) +{ + int r = obj->trunnel_error_code_; + obj->trunnel_error_code_ = 0; + return r; +} +ssize_t +signed_tree_head_v1_encode(uint8_t *output, const size_t avail, const signed_tree_head_v1_t *obj) +{ + ssize_t result = 0; + size_t written = 0; + uint8_t *ptr = output; + const char *msg; +#ifdef TRUNNEL_CHECK_ENCODED_LEN + const ssize_t encoded_len = signed_tree_head_v1_encoded_len(obj); +#endif + + if (NULL != (msg = signed_tree_head_v1_check(obj))) + goto check_failed; + +#ifdef TRUNNEL_CHECK_ENCODED_LEN + trunnel_assert(encoded_len >= 0); +#endif + + /* Encode u64 format IN [T_SIGNED_TREE_HEAD_V1] */ + trunnel_assert(written <= avail); + if (avail - written < 8) + goto truncated; + trunnel_set_uint64(ptr, trunnel_htonll(obj->format)); + written += 8; ptr += 8; + + /* Encode u64 timestamp */ + trunnel_assert(written <= avail); + if (avail - written < 8) + goto truncated; + trunnel_set_uint64(ptr, trunnel_htonll(obj->timestamp)); + written += 8; ptr += 8; + + /* Encode u64 tree_size */ + trunnel_assert(written <= avail); + if (avail - written < 8) + goto truncated; + trunnel_set_uint64(ptr, trunnel_htonll(obj->tree_size)); + written += 8; ptr += 8; + + /* Encode u8 root_hash[32] */ + trunnel_assert(written <= avail); + if (avail - written < 32) + goto truncated; + memcpy(ptr, obj->root_hash, 32); + written += 32; ptr += 32; + + /* Encode u64 length */ + trunnel_assert(written <= avail); + if (avail - written < 8) + goto truncated; + trunnel_set_uint64(ptr, trunnel_htonll(obj->length)); + written += 8; ptr += 8; + + /* Encode u8 sigident[length] */ + { + size_t elt_len = TRUNNEL_DYNARRAY_LEN(&obj->sigident); + trunnel_assert(obj->length == elt_len); + trunnel_assert(written <= avail); + if (avail - written < elt_len) + goto truncated; + if (elt_len) + memcpy(ptr, obj->sigident.elts_, elt_len); + written += elt_len; ptr += elt_len; + } + + + trunnel_assert(ptr == output + written); +#ifdef TRUNNEL_CHECK_ENCODED_LEN + { + trunnel_assert(encoded_len >= 0); + trunnel_assert((size_t)encoded_len == written); + } + +#endif + + return written; + + truncated: + result = -2; + goto fail; + check_failed: + (void)msg; + result = -1; + goto fail; + fail: + trunnel_assert(result < 0); + return result; +} + +/** As signed_tree_head_v1_parse(), but do not allocate the output + * object. + */ +static ssize_t +signed_tree_head_v1_parse_into(signed_tree_head_v1_t *obj, const uint8_t *input, const size_t len_in) +{ + const uint8_t *ptr = input; + size_t remaining = len_in; + ssize_t result = 0; + (void)result; + + /* Parse u64 format IN [T_SIGNED_TREE_HEAD_V1] */ + CHECK_REMAINING(8, truncated); + obj->format = trunnel_ntohll(trunnel_get_uint64(ptr)); + remaining -= 8; ptr += 8; + if (! (obj->format == T_SIGNED_TREE_HEAD_V1)) + goto fail; + + /* Parse u64 timestamp */ + CHECK_REMAINING(8, truncated); + obj->timestamp = trunnel_ntohll(trunnel_get_uint64(ptr)); + remaining -= 8; ptr += 8; + + /* Parse u64 tree_size */ + CHECK_REMAINING(8, truncated); + obj->tree_size = trunnel_ntohll(trunnel_get_uint64(ptr)); + remaining -= 8; ptr += 8; + + /* Parse u8 root_hash[32] */ + CHECK_REMAINING(32, truncated); + memcpy(obj->root_hash, ptr, 32); + remaining -= 32; ptr += 32; + + /* Parse u64 length */ + CHECK_REMAINING(8, truncated); + obj->length = trunnel_ntohll(trunnel_get_uint64(ptr)); + remaining -= 8; ptr += 8; + + /* Parse u8 sigident[length] */ + CHECK_REMAINING(obj->length, truncated); + TRUNNEL_DYNARRAY_EXPAND(uint8_t, &obj->sigident, obj->length, {}); + obj->sigident.n_ = obj->length; + if (obj->length) + memcpy(obj->sigident.elts_, ptr, obj->length); + ptr += obj->length; remaining -= obj->length; + trunnel_assert(ptr + remaining == input + len_in); + return len_in - remaining; + + truncated: + return -2; + trunnel_alloc_failed: + return -1; + fail: + result = -1; + return result; +} + +ssize_t +signed_tree_head_v1_parse(signed_tree_head_v1_t **output, const uint8_t *input, const size_t len_in) +{ + ssize_t result; + *output = signed_tree_head_v1_new(); + if (NULL == *output) + return -1; + result = signed_tree_head_v1_parse_into(*output, input, len_in); + if (result < 0) { + signed_tree_head_v1_free(*output); + *output = NULL; + } + return result; +} +consistency_proof_v1_t * +consistency_proof_v1_new(void) +{ + consistency_proof_v1_t *val = trunnel_calloc(1, sizeof(consistency_proof_v1_t)); + if (NULL == val) + return NULL; + val->format = T_CONSISTENCY_PROOF_V1; + return val; +} + +/** Release all storage held inside 'obj', but do not free 'obj'. + */ +static void +consistency_proof_v1_clear(consistency_proof_v1_t *obj) +{ + (void) obj; + ed25519_v1_free(obj->identifier); + obj->identifier = NULL; + TRUNNEL_DYNARRAY_WIPE(&obj->hashes); + TRUNNEL_DYNARRAY_CLEAR(&obj->hashes); +} + +void +consistency_proof_v1_free(consistency_proof_v1_t *obj) +{ + if (obj == NULL) + return; + consistency_proof_v1_clear(obj); + trunnel_memwipe(obj, sizeof(consistency_proof_v1_t)); + trunnel_free_(obj); +} + +uint64_t +consistency_proof_v1_get_format(const consistency_proof_v1_t *inp) +{ + return inp->format; +} +int +consistency_proof_v1_set_format(consistency_proof_v1_t *inp, uint64_t val) +{ + if (! ((val == T_CONSISTENCY_PROOF_V1))) { + TRUNNEL_SET_ERROR_CODE(inp); + return -1; + } + inp->format = val; + return 0; +} +struct ed25519_v1_st * +consistency_proof_v1_get_identifier(consistency_proof_v1_t *inp) +{ + return inp->identifier; +} +const struct ed25519_v1_st * +consistency_proof_v1_getconst_identifier(const consistency_proof_v1_t *inp) +{ + return consistency_proof_v1_get_identifier((consistency_proof_v1_t*) inp); +} +int +consistency_proof_v1_set_identifier(consistency_proof_v1_t *inp, struct ed25519_v1_st *val) +{ + if (inp->identifier && inp->identifier != val) + ed25519_v1_free(inp->identifier); + return consistency_proof_v1_set0_identifier(inp, val); +} +int +consistency_proof_v1_set0_identifier(consistency_proof_v1_t *inp, struct ed25519_v1_st *val) +{ + inp->identifier = val; + return 0; +} +uint64_t +consistency_proof_v1_get_old_size(const consistency_proof_v1_t *inp) +{ + return inp->old_size; +} +int +consistency_proof_v1_set_old_size(consistency_proof_v1_t *inp, uint64_t val) +{ + inp->old_size = val; + return 0; +} +uint64_t +consistency_proof_v1_get_new_size(const consistency_proof_v1_t *inp) +{ + return inp->new_size; +} +int +consistency_proof_v1_set_new_size(consistency_proof_v1_t *inp, uint64_t val) +{ + inp->new_size = val; + return 0; +} +uint64_t +consistency_proof_v1_get_length(const consistency_proof_v1_t *inp) +{ + return inp->length; +} +int +consistency_proof_v1_set_length(consistency_proof_v1_t *inp, uint64_t val) +{ + inp->length = val; + return 0; +} +size_t +consistency_proof_v1_getlen_hashes(const consistency_proof_v1_t *inp) +{ + return TRUNNEL_DYNARRAY_LEN(&inp->hashes); +} + +uint8_t +consistency_proof_v1_get_hashes(consistency_proof_v1_t *inp, size_t idx) +{ + return TRUNNEL_DYNARRAY_GET(&inp->hashes, idx); +} + +uint8_t +consistency_proof_v1_getconst_hashes(const consistency_proof_v1_t *inp, size_t idx) +{ + return consistency_proof_v1_get_hashes((consistency_proof_v1_t*)inp, idx); +} +int +consistency_proof_v1_set_hashes(consistency_proof_v1_t *inp, size_t idx, uint8_t elt) +{ + TRUNNEL_DYNARRAY_SET(&inp->hashes, idx, elt); + return 0; +} +int +consistency_proof_v1_add_hashes(consistency_proof_v1_t *inp, uint8_t elt) +{ +#if SIZE_MAX >= UINT64_MAX + if (inp->hashes.n_ == UINT64_MAX) + goto trunnel_alloc_failed; +#endif + TRUNNEL_DYNARRAY_ADD(uint8_t, &inp->hashes, elt, {}); + return 0; + trunnel_alloc_failed: + TRUNNEL_SET_ERROR_CODE(inp); + return -1; +} + +uint8_t * +consistency_proof_v1_getarray_hashes(consistency_proof_v1_t *inp) +{ + return inp->hashes.elts_; +} +const uint8_t * +consistency_proof_v1_getconstarray_hashes(const consistency_proof_v1_t *inp) +{ + return (const uint8_t *)consistency_proof_v1_getarray_hashes((consistency_proof_v1_t*)inp); +} +int +consistency_proof_v1_setlen_hashes(consistency_proof_v1_t *inp, size_t newlen) +{ + uint8_t *newptr; +#if UINT64_MAX < SIZE_MAX + if (newlen > UINT64_MAX) + goto trunnel_alloc_failed; +#endif + newptr = trunnel_dynarray_setlen(&inp->hashes.allocated_, + &inp->hashes.n_, inp->hashes.elts_, newlen, + sizeof(inp->hashes.elts_[0]), (trunnel_free_fn_t) NULL, + &inp->trunnel_error_code_); + if (newlen != 0 && newptr == NULL) + goto trunnel_alloc_failed; + inp->hashes.elts_ = newptr; + return 0; + trunnel_alloc_failed: + TRUNNEL_SET_ERROR_CODE(inp); + return -1; +} +const char * +consistency_proof_v1_check(const consistency_proof_v1_t *obj) +{ + if (obj == NULL) + return "Object was NULL"; + if (obj->trunnel_error_code_) + return "A set function failed on this object"; + if (! (obj->format == T_CONSISTENCY_PROOF_V1)) + return "Integer out of bounds"; + { + const char *msg; + if (NULL != (msg = ed25519_v1_check(obj->identifier))) + return msg; + } + if (TRUNNEL_DYNARRAY_LEN(&obj->hashes) != obj->length) + return "Length mismatch for hashes"; + return NULL; +} + +ssize_t +consistency_proof_v1_encoded_len(const consistency_proof_v1_t *obj) +{ + ssize_t result = 0; + + if (NULL != consistency_proof_v1_check(obj)) + return -1; + + + /* Length of u64 format IN [T_CONSISTENCY_PROOF_V1] */ + result += 8; + + /* Length of struct ed25519_v1 identifier */ + result += ed25519_v1_encoded_len(obj->identifier); + + /* Length of u64 old_size */ + result += 8; + + /* Length of u64 new_size */ + result += 8; + + /* Length of u64 length */ + result += 8; + + /* Length of u8 hashes[length] */ + result += TRUNNEL_DYNARRAY_LEN(&obj->hashes); + return result; +} +int +consistency_proof_v1_clear_errors(consistency_proof_v1_t *obj) +{ + int r = obj->trunnel_error_code_; + obj->trunnel_error_code_ = 0; + return r; +} +ssize_t +consistency_proof_v1_encode(uint8_t *output, const size_t avail, const consistency_proof_v1_t *obj) +{ + ssize_t result = 0; + size_t written = 0; + uint8_t *ptr = output; + const char *msg; +#ifdef TRUNNEL_CHECK_ENCODED_LEN + const ssize_t encoded_len = consistency_proof_v1_encoded_len(obj); +#endif + + if (NULL != (msg = consistency_proof_v1_check(obj))) + goto check_failed; + +#ifdef TRUNNEL_CHECK_ENCODED_LEN + trunnel_assert(encoded_len >= 0); +#endif + + /* Encode u64 format IN [T_CONSISTENCY_PROOF_V1] */ + trunnel_assert(written <= avail); + if (avail - written < 8) + goto truncated; + trunnel_set_uint64(ptr, trunnel_htonll(obj->format)); + written += 8; ptr += 8; + + /* Encode struct ed25519_v1 identifier */ + trunnel_assert(written <= avail); + result = ed25519_v1_encode(ptr, avail - written, obj->identifier); + if (result < 0) + goto fail; /* XXXXXXX !*/ + written += result; ptr += result; + + /* Encode u64 old_size */ + trunnel_assert(written <= avail); + if (avail - written < 8) + goto truncated; + trunnel_set_uint64(ptr, trunnel_htonll(obj->old_size)); + written += 8; ptr += 8; + + /* Encode u64 new_size */ + trunnel_assert(written <= avail); + if (avail - written < 8) + goto truncated; + trunnel_set_uint64(ptr, trunnel_htonll(obj->new_size)); + written += 8; ptr += 8; + + /* Encode u64 length */ + trunnel_assert(written <= avail); + if (avail - written < 8) + goto truncated; + trunnel_set_uint64(ptr, trunnel_htonll(obj->length)); + written += 8; ptr += 8; + + /* Encode u8 hashes[length] */ + { + size_t elt_len = TRUNNEL_DYNARRAY_LEN(&obj->hashes); + trunnel_assert(obj->length == elt_len); + trunnel_assert(written <= avail); + if (avail - written < elt_len) + goto truncated; + if (elt_len) + memcpy(ptr, obj->hashes.elts_, elt_len); + written += elt_len; ptr += elt_len; + } + + + trunnel_assert(ptr == output + written); +#ifdef TRUNNEL_CHECK_ENCODED_LEN + { + trunnel_assert(encoded_len >= 0); + trunnel_assert((size_t)encoded_len == written); + } + +#endif + + return written; + + truncated: + result = -2; + goto fail; + check_failed: + (void)msg; + result = -1; + goto fail; + fail: + trunnel_assert(result < 0); + return result; +} + +/** As consistency_proof_v1_parse(), but do not allocate the output + * object. + */ +static ssize_t +consistency_proof_v1_parse_into(consistency_proof_v1_t *obj, const uint8_t *input, const size_t len_in) +{ + const uint8_t *ptr = input; + size_t remaining = len_in; + ssize_t result = 0; + (void)result; + + /* Parse u64 format IN [T_CONSISTENCY_PROOF_V1] */ + CHECK_REMAINING(8, truncated); + obj->format = trunnel_ntohll(trunnel_get_uint64(ptr)); + remaining -= 8; ptr += 8; + if (! (obj->format == T_CONSISTENCY_PROOF_V1)) + goto fail; + + /* Parse struct ed25519_v1 identifier */ + result = ed25519_v1_parse(&obj->identifier, ptr, remaining); + if (result < 0) + goto relay_fail; + trunnel_assert((size_t)result <= remaining); + remaining -= result; ptr += result; + + /* Parse u64 old_size */ + CHECK_REMAINING(8, truncated); + obj->old_size = trunnel_ntohll(trunnel_get_uint64(ptr)); + remaining -= 8; ptr += 8; + + /* Parse u64 new_size */ + CHECK_REMAINING(8, truncated); + obj->new_size = trunnel_ntohll(trunnel_get_uint64(ptr)); + remaining -= 8; ptr += 8; + + /* Parse u64 length */ + CHECK_REMAINING(8, truncated); + obj->length = trunnel_ntohll(trunnel_get_uint64(ptr)); + remaining -= 8; ptr += 8; + + /* Parse u8 hashes[length] */ + CHECK_REMAINING(obj->length, truncated); + TRUNNEL_DYNARRAY_EXPAND(uint8_t, &obj->hashes, obj->length, {}); + obj->hashes.n_ = obj->length; + if (obj->length) + memcpy(obj->hashes.elts_, ptr, obj->length); + ptr += obj->length; remaining -= obj->length; + trunnel_assert(ptr + remaining == input + len_in); + return len_in - remaining; + + truncated: + return -2; + relay_fail: + trunnel_assert(result < 0); + return result; + trunnel_alloc_failed: + return -1; + fail: + result = -1; + return result; +} + +ssize_t +consistency_proof_v1_parse(consistency_proof_v1_t **output, const uint8_t *input, const size_t len_in) +{ + ssize_t result; + *output = consistency_proof_v1_new(); + if (NULL == *output) + return -1; + result = consistency_proof_v1_parse_into(*output, input, len_in); + if (result < 0) { + consistency_proof_v1_free(*output); + *output = NULL; + } + return result; +} +inclusion_proof_v1_t * +inclusion_proof_v1_new(void) +{ + inclusion_proof_v1_t *val = trunnel_calloc(1, sizeof(inclusion_proof_v1_t)); + if (NULL == val) + return NULL; + val->format = T_INCLUSION_PROOF_V1; + return val; +} + +/** Release all storage held inside 'obj', but do not free 'obj'. + */ +static void +inclusion_proof_v1_clear(inclusion_proof_v1_t *obj) +{ + (void) obj; + ed25519_v1_free(obj->identifier); + obj->identifier = NULL; + TRUNNEL_DYNARRAY_WIPE(&obj->hashes); + TRUNNEL_DYNARRAY_CLEAR(&obj->hashes); +} + +void +inclusion_proof_v1_free(inclusion_proof_v1_t *obj) +{ + if (obj == NULL) + return; + inclusion_proof_v1_clear(obj); + trunnel_memwipe(obj, sizeof(inclusion_proof_v1_t)); + trunnel_free_(obj); +} + +uint64_t +inclusion_proof_v1_get_format(const inclusion_proof_v1_t *inp) +{ + return inp->format; +} +int +inclusion_proof_v1_set_format(inclusion_proof_v1_t *inp, uint64_t val) +{ + if (! ((val == T_INCLUSION_PROOF_V1))) { + TRUNNEL_SET_ERROR_CODE(inp); + return -1; + } + inp->format = val; + return 0; +} +struct ed25519_v1_st * +inclusion_proof_v1_get_identifier(inclusion_proof_v1_t *inp) +{ + return inp->identifier; +} +const struct ed25519_v1_st * +inclusion_proof_v1_getconst_identifier(const inclusion_proof_v1_t *inp) +{ + return inclusion_proof_v1_get_identifier((inclusion_proof_v1_t*) inp); +} +int +inclusion_proof_v1_set_identifier(inclusion_proof_v1_t *inp, struct ed25519_v1_st *val) +{ + if (inp->identifier && inp->identifier != val) + ed25519_v1_free(inp->identifier); + return inclusion_proof_v1_set0_identifier(inp, val); +} +int +inclusion_proof_v1_set0_identifier(inclusion_proof_v1_t *inp, struct ed25519_v1_st *val) +{ + inp->identifier = val; + return 0; +} +uint64_t +inclusion_proof_v1_get_tree_size(const inclusion_proof_v1_t *inp) +{ + return inp->tree_size; +} +int +inclusion_proof_v1_set_tree_size(inclusion_proof_v1_t *inp, uint64_t val) +{ + inp->tree_size = val; + return 0; +} +uint64_t +inclusion_proof_v1_get_leaf_index(const inclusion_proof_v1_t *inp) +{ + return inp->leaf_index; +} +int +inclusion_proof_v1_set_leaf_index(inclusion_proof_v1_t *inp, uint64_t val) +{ + inp->leaf_index = val; + return 0; +} +uint64_t +inclusion_proof_v1_get_length(const inclusion_proof_v1_t *inp) +{ + return inp->length; +} +int +inclusion_proof_v1_set_length(inclusion_proof_v1_t *inp, uint64_t val) +{ + inp->length = val; + return 0; +} +size_t +inclusion_proof_v1_getlen_hashes(const inclusion_proof_v1_t *inp) +{ + return TRUNNEL_DYNARRAY_LEN(&inp->hashes); +} + +uint8_t +inclusion_proof_v1_get_hashes(inclusion_proof_v1_t *inp, size_t idx) +{ + return TRUNNEL_DYNARRAY_GET(&inp->hashes, idx); +} + +uint8_t +inclusion_proof_v1_getconst_hashes(const inclusion_proof_v1_t *inp, size_t idx) +{ + return inclusion_proof_v1_get_hashes((inclusion_proof_v1_t*)inp, idx); +} +int +inclusion_proof_v1_set_hashes(inclusion_proof_v1_t *inp, size_t idx, uint8_t elt) +{ + TRUNNEL_DYNARRAY_SET(&inp->hashes, idx, elt); + return 0; +} +int +inclusion_proof_v1_add_hashes(inclusion_proof_v1_t *inp, uint8_t elt) +{ +#if SIZE_MAX >= UINT64_MAX + if (inp->hashes.n_ == UINT64_MAX) + goto trunnel_alloc_failed; +#endif + TRUNNEL_DYNARRAY_ADD(uint8_t, &inp->hashes, elt, {}); + return 0; + trunnel_alloc_failed: + TRUNNEL_SET_ERROR_CODE(inp); + return -1; +} + +uint8_t * +inclusion_proof_v1_getarray_hashes(inclusion_proof_v1_t *inp) +{ + return inp->hashes.elts_; +} +const uint8_t * +inclusion_proof_v1_getconstarray_hashes(const inclusion_proof_v1_t *inp) +{ + return (const uint8_t *)inclusion_proof_v1_getarray_hashes((inclusion_proof_v1_t*)inp); +} +int +inclusion_proof_v1_setlen_hashes(inclusion_proof_v1_t *inp, size_t newlen) +{ + uint8_t *newptr; +#if UINT64_MAX < SIZE_MAX + if (newlen > UINT64_MAX) + goto trunnel_alloc_failed; +#endif + newptr = trunnel_dynarray_setlen(&inp->hashes.allocated_, + &inp->hashes.n_, inp->hashes.elts_, newlen, + sizeof(inp->hashes.elts_[0]), (trunnel_free_fn_t) NULL, + &inp->trunnel_error_code_); + if (newlen != 0 && newptr == NULL) + goto trunnel_alloc_failed; + inp->hashes.elts_ = newptr; + return 0; + trunnel_alloc_failed: + TRUNNEL_SET_ERROR_CODE(inp); + return -1; +} +const char * +inclusion_proof_v1_check(const inclusion_proof_v1_t *obj) +{ + if (obj == NULL) + return "Object was NULL"; + if (obj->trunnel_error_code_) + return "A set function failed on this object"; + if (! (obj->format == T_INCLUSION_PROOF_V1)) + return "Integer out of bounds"; + { + const char *msg; + if (NULL != (msg = ed25519_v1_check(obj->identifier))) + return msg; + } + if (TRUNNEL_DYNARRAY_LEN(&obj->hashes) != obj->length) + return "Length mismatch for hashes"; + return NULL; +} + +ssize_t +inclusion_proof_v1_encoded_len(const inclusion_proof_v1_t *obj) +{ + ssize_t result = 0; + + if (NULL != inclusion_proof_v1_check(obj)) + return -1; + + + /* Length of u64 format IN [T_INCLUSION_PROOF_V1] */ + result += 8; + + /* Length of struct ed25519_v1 identifier */ + result += ed25519_v1_encoded_len(obj->identifier); + + /* Length of u64 tree_size */ + result += 8; + + /* Length of u64 leaf_index */ + result += 8; + + /* Length of u64 length */ + result += 8; + + /* Length of u8 hashes[length] */ + result += TRUNNEL_DYNARRAY_LEN(&obj->hashes); + return result; +} +int +inclusion_proof_v1_clear_errors(inclusion_proof_v1_t *obj) +{ + int r = obj->trunnel_error_code_; + obj->trunnel_error_code_ = 0; + return r; +} +ssize_t +inclusion_proof_v1_encode(uint8_t *output, const size_t avail, const inclusion_proof_v1_t *obj) +{ + ssize_t result = 0; + size_t written = 0; + uint8_t *ptr = output; + const char *msg; +#ifdef TRUNNEL_CHECK_ENCODED_LEN + const ssize_t encoded_len = inclusion_proof_v1_encoded_len(obj); +#endif + + if (NULL != (msg = inclusion_proof_v1_check(obj))) + goto check_failed; + +#ifdef TRUNNEL_CHECK_ENCODED_LEN + trunnel_assert(encoded_len >= 0); +#endif + + /* Encode u64 format IN [T_INCLUSION_PROOF_V1] */ + trunnel_assert(written <= avail); + if (avail - written < 8) + goto truncated; + trunnel_set_uint64(ptr, trunnel_htonll(obj->format)); + written += 8; ptr += 8; + + /* Encode struct ed25519_v1 identifier */ + trunnel_assert(written <= avail); + result = ed25519_v1_encode(ptr, avail - written, obj->identifier); + if (result < 0) + goto fail; /* XXXXXXX !*/ + written += result; ptr += result; + + /* Encode u64 tree_size */ + trunnel_assert(written <= avail); + if (avail - written < 8) + goto truncated; + trunnel_set_uint64(ptr, trunnel_htonll(obj->tree_size)); + written += 8; ptr += 8; + + /* Encode u64 leaf_index */ + trunnel_assert(written <= avail); + if (avail - written < 8) + goto truncated; + trunnel_set_uint64(ptr, trunnel_htonll(obj->leaf_index)); + written += 8; ptr += 8; + + /* Encode u64 length */ + trunnel_assert(written <= avail); + if (avail - written < 8) + goto truncated; + trunnel_set_uint64(ptr, trunnel_htonll(obj->length)); + written += 8; ptr += 8; + + /* Encode u8 hashes[length] */ + { + size_t elt_len = TRUNNEL_DYNARRAY_LEN(&obj->hashes); + trunnel_assert(obj->length == elt_len); + trunnel_assert(written <= avail); + if (avail - written < elt_len) + goto truncated; + if (elt_len) + memcpy(ptr, obj->hashes.elts_, elt_len); + written += elt_len; ptr += elt_len; + } + + + trunnel_assert(ptr == output + written); +#ifdef TRUNNEL_CHECK_ENCODED_LEN + { + trunnel_assert(encoded_len >= 0); + trunnel_assert((size_t)encoded_len == written); + } + +#endif + + return written; + + truncated: + result = -2; + goto fail; + check_failed: + (void)msg; + result = -1; + goto fail; + fail: + trunnel_assert(result < 0); + return result; +} + +/** As inclusion_proof_v1_parse(), but do not allocate the output + * object. + */ +static ssize_t +inclusion_proof_v1_parse_into(inclusion_proof_v1_t *obj, const uint8_t *input, const size_t len_in) +{ + const uint8_t *ptr = input; + size_t remaining = len_in; + ssize_t result = 0; + (void)result; + + /* Parse u64 format IN [T_INCLUSION_PROOF_V1] */ + CHECK_REMAINING(8, truncated); + obj->format = trunnel_ntohll(trunnel_get_uint64(ptr)); + remaining -= 8; ptr += 8; + if (! (obj->format == T_INCLUSION_PROOF_V1)) + goto fail; + + /* Parse struct ed25519_v1 identifier */ + result = ed25519_v1_parse(&obj->identifier, ptr, remaining); + if (result < 0) + goto relay_fail; + trunnel_assert((size_t)result <= remaining); + remaining -= result; ptr += result; + + /* Parse u64 tree_size */ + CHECK_REMAINING(8, truncated); + obj->tree_size = trunnel_ntohll(trunnel_get_uint64(ptr)); + remaining -= 8; ptr += 8; + + /* Parse u64 leaf_index */ + CHECK_REMAINING(8, truncated); + obj->leaf_index = trunnel_ntohll(trunnel_get_uint64(ptr)); + remaining -= 8; ptr += 8; + + /* Parse u64 length */ + CHECK_REMAINING(8, truncated); + obj->length = trunnel_ntohll(trunnel_get_uint64(ptr)); + remaining -= 8; ptr += 8; + + /* Parse u8 hashes[length] */ + CHECK_REMAINING(obj->length, truncated); + TRUNNEL_DYNARRAY_EXPAND(uint8_t, &obj->hashes, obj->length, {}); + obj->hashes.n_ = obj->length; + if (obj->length) + memcpy(obj->hashes.elts_, ptr, obj->length); + ptr += obj->length; remaining -= obj->length; + trunnel_assert(ptr + remaining == input + len_in); + return len_in - remaining; + + truncated: + return -2; + relay_fail: + trunnel_assert(result < 0); + return result; + trunnel_alloc_failed: + return -1; + fail: + result = -1; + return result; +} + +ssize_t +inclusion_proof_v1_parse(inclusion_proof_v1_t **output, const uint8_t *input, const size_t len_in) +{ + ssize_t result; + *output = inclusion_proof_v1_new(); + if (NULL == *output) + return -1; + result = inclusion_proof_v1_parse_into(*output, input, len_in); + if (result < 0) { + inclusion_proof_v1_free(*output); + *output = NULL; + } + return result; +} +request_v1_t * +request_v1_new(void) +{ + request_v1_t *val = trunnel_calloc(1, sizeof(request_v1_t)); + if (NULL == val) + return NULL; + val->format = T_GET_CONSISTENCY_PROOF_V1; + return val; +} + +/** Release all storage held inside 'obj', but do not free 'obj'. + */ +static void +request_v1_clear(request_v1_t *obj) +{ + (void) obj; + req_get_entries_v1_free(obj->request_get_entries); + obj->request_get_entries = NULL; + req_get_proof_by_hash_v1_free(obj->request_get_proof_by_hash); + obj->request_get_proof_by_hash = NULL; + req_get_consistency_proof_v1_free(obj->request_get_consistency_proof); + obj->request_get_consistency_proof = NULL; +} + +void +request_v1_free(request_v1_t *obj) +{ + if (obj == NULL) + return; + request_v1_clear(obj); + trunnel_memwipe(obj, sizeof(request_v1_t)); + trunnel_free_(obj); +} + +uint64_t +request_v1_get_format(const request_v1_t *inp) +{ + return inp->format; +} +int +request_v1_set_format(request_v1_t *inp, uint64_t val) +{ + if (! ((val == T_GET_CONSISTENCY_PROOF_V1 || val == T_GET_ENTRIES_V1 || val == T_GET_PROOF_BY_HASH_V1))) { + TRUNNEL_SET_ERROR_CODE(inp); + return -1; + } + inp->format = val; + return 0; +} +struct req_get_entries_v1_st * +request_v1_get_request_get_entries(request_v1_t *inp) +{ + return inp->request_get_entries; +} +const struct req_get_entries_v1_st * +request_v1_getconst_request_get_entries(const request_v1_t *inp) +{ + return request_v1_get_request_get_entries((request_v1_t*) inp); +} +int +request_v1_set_request_get_entries(request_v1_t *inp, struct req_get_entries_v1_st *val) +{ + if (inp->request_get_entries && inp->request_get_entries != val) + req_get_entries_v1_free(inp->request_get_entries); + return request_v1_set0_request_get_entries(inp, val); +} +int +request_v1_set0_request_get_entries(request_v1_t *inp, struct req_get_entries_v1_st *val) +{ + inp->request_get_entries = val; + return 0; +} +struct req_get_proof_by_hash_v1_st * +request_v1_get_request_get_proof_by_hash(request_v1_t *inp) +{ + return inp->request_get_proof_by_hash; +} +const struct req_get_proof_by_hash_v1_st * +request_v1_getconst_request_get_proof_by_hash(const request_v1_t *inp) +{ + return request_v1_get_request_get_proof_by_hash((request_v1_t*) inp); +} +int +request_v1_set_request_get_proof_by_hash(request_v1_t *inp, struct req_get_proof_by_hash_v1_st *val) +{ + if (inp->request_get_proof_by_hash && inp->request_get_proof_by_hash != val) + req_get_proof_by_hash_v1_free(inp->request_get_proof_by_hash); + return request_v1_set0_request_get_proof_by_hash(inp, val); +} +int +request_v1_set0_request_get_proof_by_hash(request_v1_t *inp, struct req_get_proof_by_hash_v1_st *val) +{ + inp->request_get_proof_by_hash = val; + return 0; +} +struct req_get_consistency_proof_v1_st * +request_v1_get_request_get_consistency_proof(request_v1_t *inp) +{ + return inp->request_get_consistency_proof; +} +const struct req_get_consistency_proof_v1_st * +request_v1_getconst_request_get_consistency_proof(const request_v1_t *inp) +{ + return request_v1_get_request_get_consistency_proof((request_v1_t*) inp); +} +int +request_v1_set_request_get_consistency_proof(request_v1_t *inp, struct req_get_consistency_proof_v1_st *val) +{ + if (inp->request_get_consistency_proof && inp->request_get_consistency_proof != val) + req_get_consistency_proof_v1_free(inp->request_get_consistency_proof); + return request_v1_set0_request_get_consistency_proof(inp, val); +} +int +request_v1_set0_request_get_consistency_proof(request_v1_t *inp, struct req_get_consistency_proof_v1_st *val) +{ + inp->request_get_consistency_proof = val; + return 0; +} +const char * +request_v1_check(const request_v1_t *obj) +{ + if (obj == NULL) + return "Object was NULL"; + if (obj->trunnel_error_code_) + return "A set function failed on this object"; + if (! (obj->format == T_GET_CONSISTENCY_PROOF_V1 || obj->format == T_GET_ENTRIES_V1 || obj->format == T_GET_PROOF_BY_HASH_V1)) + return "Integer out of bounds"; + switch (obj->format) { + + case T_GET_ENTRIES_V1: + { + const char *msg; + if (NULL != (msg = req_get_entries_v1_check(obj->request_get_entries))) + return msg; + } + break; + + case T_GET_PROOF_BY_HASH_V1: + { + const char *msg; + if (NULL != (msg = req_get_proof_by_hash_v1_check(obj->request_get_proof_by_hash))) + return msg; + } + break; + + case T_GET_CONSISTENCY_PROOF_V1: + { + const char *msg; + if (NULL != (msg = req_get_consistency_proof_v1_check(obj->request_get_consistency_proof))) + return msg; + } + break; + + default: + return "Bad tag for union"; + break; + } + return NULL; +} + +ssize_t +request_v1_encoded_len(const request_v1_t *obj) +{ + ssize_t result = 0; + + if (NULL != request_v1_check(obj)) + return -1; + + + /* Length of u64 format IN [T_GET_CONSISTENCY_PROOF_V1, T_GET_ENTRIES_V1, T_GET_PROOF_BY_HASH_V1] */ + result += 8; + switch (obj->format) { + + case T_GET_ENTRIES_V1: + + /* Length of struct req_get_entries_v1 request_get_entries */ + result += req_get_entries_v1_encoded_len(obj->request_get_entries); + break; + + case T_GET_PROOF_BY_HASH_V1: + + /* Length of struct req_get_proof_by_hash_v1 request_get_proof_by_hash */ + result += req_get_proof_by_hash_v1_encoded_len(obj->request_get_proof_by_hash); + break; + + case T_GET_CONSISTENCY_PROOF_V1: + + /* Length of struct req_get_consistency_proof_v1 request_get_consistency_proof */ + result += req_get_consistency_proof_v1_encoded_len(obj->request_get_consistency_proof); + break; + + default: + trunnel_assert(0); + break; + } + return result; +} +int +request_v1_clear_errors(request_v1_t *obj) +{ + int r = obj->trunnel_error_code_; + obj->trunnel_error_code_ = 0; + return r; +} +ssize_t +request_v1_encode(uint8_t *output, const size_t avail, const request_v1_t *obj) +{ + ssize_t result = 0; + size_t written = 0; + uint8_t *ptr = output; + const char *msg; +#ifdef TRUNNEL_CHECK_ENCODED_LEN + const ssize_t encoded_len = request_v1_encoded_len(obj); +#endif + + if (NULL != (msg = request_v1_check(obj))) + goto check_failed; + +#ifdef TRUNNEL_CHECK_ENCODED_LEN + trunnel_assert(encoded_len >= 0); +#endif + + /* Encode u64 format IN [T_GET_CONSISTENCY_PROOF_V1, T_GET_ENTRIES_V1, T_GET_PROOF_BY_HASH_V1] */ + trunnel_assert(written <= avail); + if (avail - written < 8) + goto truncated; + trunnel_set_uint64(ptr, trunnel_htonll(obj->format)); + written += 8; ptr += 8; + + /* Encode union request[format] */ + trunnel_assert(written <= avail); + switch (obj->format) { + + case T_GET_ENTRIES_V1: + + /* Encode struct req_get_entries_v1 request_get_entries */ + trunnel_assert(written <= avail); + result = req_get_entries_v1_encode(ptr, avail - written, obj->request_get_entries); + if (result < 0) + goto fail; /* XXXXXXX !*/ + written += result; ptr += result; + break; + + case T_GET_PROOF_BY_HASH_V1: + + /* Encode struct req_get_proof_by_hash_v1 request_get_proof_by_hash */ + trunnel_assert(written <= avail); + result = req_get_proof_by_hash_v1_encode(ptr, avail - written, obj->request_get_proof_by_hash); + if (result < 0) + goto fail; /* XXXXXXX !*/ + written += result; ptr += result; + break; + + case T_GET_CONSISTENCY_PROOF_V1: + + /* Encode struct req_get_consistency_proof_v1 request_get_consistency_proof */ + trunnel_assert(written <= avail); + result = req_get_consistency_proof_v1_encode(ptr, avail - written, obj->request_get_consistency_proof); + if (result < 0) + goto fail; /* XXXXXXX !*/ + written += result; ptr += result; + break; + + default: + trunnel_assert(0); + break; + } + + + trunnel_assert(ptr == output + written); +#ifdef TRUNNEL_CHECK_ENCODED_LEN + { + trunnel_assert(encoded_len >= 0); + trunnel_assert((size_t)encoded_len == written); + } + +#endif + + return written; + + truncated: + result = -2; + goto fail; + check_failed: + (void)msg; + result = -1; + goto fail; + fail: + trunnel_assert(result < 0); + return result; +} + +/** As request_v1_parse(), but do not allocate the output object. + */ +static ssize_t +request_v1_parse_into(request_v1_t *obj, const uint8_t *input, const size_t len_in) +{ + const uint8_t *ptr = input; + size_t remaining = len_in; + ssize_t result = 0; + (void)result; + + /* Parse u64 format IN [T_GET_CONSISTENCY_PROOF_V1, T_GET_ENTRIES_V1, T_GET_PROOF_BY_HASH_V1] */ + CHECK_REMAINING(8, truncated); + obj->format = trunnel_ntohll(trunnel_get_uint64(ptr)); + remaining -= 8; ptr += 8; + if (! (obj->format == T_GET_CONSISTENCY_PROOF_V1 || obj->format == T_GET_ENTRIES_V1 || obj->format == T_GET_PROOF_BY_HASH_V1)) + goto fail; + + /* Parse union request[format] */ + switch (obj->format) { + + case T_GET_ENTRIES_V1: + + /* Parse struct req_get_entries_v1 request_get_entries */ + result = req_get_entries_v1_parse(&obj->request_get_entries, ptr, remaining); + if (result < 0) + goto relay_fail; + trunnel_assert((size_t)result <= remaining); + remaining -= result; ptr += result; + break; + + case T_GET_PROOF_BY_HASH_V1: + + /* Parse struct req_get_proof_by_hash_v1 request_get_proof_by_hash */ + result = req_get_proof_by_hash_v1_parse(&obj->request_get_proof_by_hash, ptr, remaining); + if (result < 0) + goto relay_fail; + trunnel_assert((size_t)result <= remaining); + remaining -= result; ptr += result; + break; + + case T_GET_CONSISTENCY_PROOF_V1: + + /* Parse struct req_get_consistency_proof_v1 request_get_consistency_proof */ + result = req_get_consistency_proof_v1_parse(&obj->request_get_consistency_proof, ptr, remaining); + if (result < 0) + goto relay_fail; + trunnel_assert((size_t)result <= remaining); + remaining -= result; ptr += result; + break; + + default: + goto fail; + break; + } + trunnel_assert(ptr + remaining == input + len_in); + return len_in - remaining; + + truncated: + return -2; + relay_fail: + trunnel_assert(result < 0); + return result; + fail: + result = -1; + return result; +} + +ssize_t +request_v1_parse(request_v1_t **output, const uint8_t *input, const size_t len_in) +{ + ssize_t result; + *output = request_v1_new(); + if (NULL == *output) + return -1; + result = request_v1_parse_into(*output, input, len_in); + if (result < 0) { + request_v1_free(*output); + *output = NULL; + } + return result; +} +signed_checksum32_ed25519_v1_t * +signed_checksum32_ed25519_v1_new(void) +{ + signed_checksum32_ed25519_v1_t *val = trunnel_calloc(1, sizeof(signed_checksum32_ed25519_v1_t)); + if (NULL == val) + return NULL; + val->format = T_SIGNED_CHECKSUM32_ED25519_V1; + val->length = 1; + return val; +} + +/** Release all storage held inside 'obj', but do not free 'obj'. + */ +static void +signed_checksum32_ed25519_v1_clear(signed_checksum32_ed25519_v1_t *obj) +{ + (void) obj; + TRUNNEL_DYNARRAY_WIPE(&obj->identifier); + TRUNNEL_DYNARRAY_CLEAR(&obj->identifier); + ed25519_v1_free(obj->namespace); + obj->namespace = NULL; +} + +void +signed_checksum32_ed25519_v1_free(signed_checksum32_ed25519_v1_t *obj) +{ + if (obj == NULL) + return; + signed_checksum32_ed25519_v1_clear(obj); + trunnel_memwipe(obj, sizeof(signed_checksum32_ed25519_v1_t)); + trunnel_free_(obj); +} + +uint64_t +signed_checksum32_ed25519_v1_get_format(const signed_checksum32_ed25519_v1_t *inp) +{ + return inp->format; +} +int +signed_checksum32_ed25519_v1_set_format(signed_checksum32_ed25519_v1_t *inp, uint64_t val) +{ + if (! ((val == T_SIGNED_CHECKSUM32_ED25519_V1))) { + TRUNNEL_SET_ERROR_CODE(inp); + return -1; + } + inp->format = val; + return 0; +} +size_t +signed_checksum32_ed25519_v1_getlen_checksum(const signed_checksum32_ed25519_v1_t *inp) +{ + (void)inp; return 32; +} + +uint8_t +signed_checksum32_ed25519_v1_get_checksum(signed_checksum32_ed25519_v1_t *inp, size_t idx) +{ + trunnel_assert(idx < 32); + return inp->checksum[idx]; +} + +uint8_t +signed_checksum32_ed25519_v1_getconst_checksum(const signed_checksum32_ed25519_v1_t *inp, size_t idx) +{ + return signed_checksum32_ed25519_v1_get_checksum((signed_checksum32_ed25519_v1_t*)inp, idx); +} +int +signed_checksum32_ed25519_v1_set_checksum(signed_checksum32_ed25519_v1_t *inp, size_t idx, uint8_t elt) +{ + trunnel_assert(idx < 32); + inp->checksum[idx] = elt; + return 0; +} + +uint8_t * +signed_checksum32_ed25519_v1_getarray_checksum(signed_checksum32_ed25519_v1_t *inp) +{ + return inp->checksum; +} +const uint8_t * +signed_checksum32_ed25519_v1_getconstarray_checksum(const signed_checksum32_ed25519_v1_t *inp) +{ + return (const uint8_t *)signed_checksum32_ed25519_v1_getarray_checksum((signed_checksum32_ed25519_v1_t*)inp); +} +uint64_t +signed_checksum32_ed25519_v1_get_length(const signed_checksum32_ed25519_v1_t *inp) +{ + return inp->length; +} +int +signed_checksum32_ed25519_v1_set_length(signed_checksum32_ed25519_v1_t *inp, uint64_t val) +{ + if (! (((val >= 1 && val <= 127)))) { + TRUNNEL_SET_ERROR_CODE(inp); + return -1; + } + inp->length = val; + return 0; +} +size_t +signed_checksum32_ed25519_v1_getlen_identifier(const signed_checksum32_ed25519_v1_t *inp) +{ + return TRUNNEL_DYNARRAY_LEN(&inp->identifier); +} + +uint8_t +signed_checksum32_ed25519_v1_get_identifier(signed_checksum32_ed25519_v1_t *inp, size_t idx) +{ + return TRUNNEL_DYNARRAY_GET(&inp->identifier, idx); +} + +uint8_t +signed_checksum32_ed25519_v1_getconst_identifier(const signed_checksum32_ed25519_v1_t *inp, size_t idx) +{ + return signed_checksum32_ed25519_v1_get_identifier((signed_checksum32_ed25519_v1_t*)inp, idx); +} +int +signed_checksum32_ed25519_v1_set_identifier(signed_checksum32_ed25519_v1_t *inp, size_t idx, uint8_t elt) +{ + TRUNNEL_DYNARRAY_SET(&inp->identifier, idx, elt); + return 0; +} +int +signed_checksum32_ed25519_v1_add_identifier(signed_checksum32_ed25519_v1_t *inp, uint8_t elt) +{ +#if SIZE_MAX >= UINT64_MAX + if (inp->identifier.n_ == UINT64_MAX) + goto trunnel_alloc_failed; +#endif + TRUNNEL_DYNARRAY_ADD(uint8_t, &inp->identifier, elt, {}); + return 0; + trunnel_alloc_failed: + TRUNNEL_SET_ERROR_CODE(inp); + return -1; +} + +uint8_t * +signed_checksum32_ed25519_v1_getarray_identifier(signed_checksum32_ed25519_v1_t *inp) +{ + return inp->identifier.elts_; +} +const uint8_t * +signed_checksum32_ed25519_v1_getconstarray_identifier(const signed_checksum32_ed25519_v1_t *inp) +{ + return (const uint8_t *)signed_checksum32_ed25519_v1_getarray_identifier((signed_checksum32_ed25519_v1_t*)inp); +} +int +signed_checksum32_ed25519_v1_setlen_identifier(signed_checksum32_ed25519_v1_t *inp, size_t newlen) +{ + uint8_t *newptr; +#if UINT64_MAX < SIZE_MAX + if (newlen > UINT64_MAX) + goto trunnel_alloc_failed; +#endif + newptr = trunnel_dynarray_setlen(&inp->identifier.allocated_, + &inp->identifier.n_, inp->identifier.elts_, newlen, + sizeof(inp->identifier.elts_[0]), (trunnel_free_fn_t) NULL, + &inp->trunnel_error_code_); + if (newlen != 0 && newptr == NULL) + goto trunnel_alloc_failed; + inp->identifier.elts_ = newptr; + return 0; + trunnel_alloc_failed: + TRUNNEL_SET_ERROR_CODE(inp); + return -1; +} +size_t +signed_checksum32_ed25519_v1_getlen_signature(const signed_checksum32_ed25519_v1_t *inp) +{ + (void)inp; return 64; +} + +uint8_t +signed_checksum32_ed25519_v1_get_signature(signed_checksum32_ed25519_v1_t *inp, size_t idx) +{ + trunnel_assert(idx < 64); + return inp->signature[idx]; +} + +uint8_t +signed_checksum32_ed25519_v1_getconst_signature(const signed_checksum32_ed25519_v1_t *inp, size_t idx) +{ + return signed_checksum32_ed25519_v1_get_signature((signed_checksum32_ed25519_v1_t*)inp, idx); +} +int +signed_checksum32_ed25519_v1_set_signature(signed_checksum32_ed25519_v1_t *inp, size_t idx, uint8_t elt) +{ + trunnel_assert(idx < 64); + inp->signature[idx] = elt; + return 0; +} + +uint8_t * +signed_checksum32_ed25519_v1_getarray_signature(signed_checksum32_ed25519_v1_t *inp) +{ + return inp->signature; +} +const uint8_t * +signed_checksum32_ed25519_v1_getconstarray_signature(const signed_checksum32_ed25519_v1_t *inp) +{ + return (const uint8_t *)signed_checksum32_ed25519_v1_getarray_signature((signed_checksum32_ed25519_v1_t*)inp); +} +struct ed25519_v1_st * +signed_checksum32_ed25519_v1_get_namespace(signed_checksum32_ed25519_v1_t *inp) +{ + return inp->namespace; +} +const struct ed25519_v1_st * +signed_checksum32_ed25519_v1_getconst_namespace(const signed_checksum32_ed25519_v1_t *inp) +{ + return signed_checksum32_ed25519_v1_get_namespace((signed_checksum32_ed25519_v1_t*) inp); +} +int +signed_checksum32_ed25519_v1_set_namespace(signed_checksum32_ed25519_v1_t *inp, struct ed25519_v1_st *val) +{ + if (inp->namespace && inp->namespace != val) + ed25519_v1_free(inp->namespace); + return signed_checksum32_ed25519_v1_set0_namespace(inp, val); +} +int +signed_checksum32_ed25519_v1_set0_namespace(signed_checksum32_ed25519_v1_t *inp, struct ed25519_v1_st *val) +{ + inp->namespace = val; + return 0; +} +const char * +signed_checksum32_ed25519_v1_check(const signed_checksum32_ed25519_v1_t *obj) +{ + if (obj == NULL) + return "Object was NULL"; + if (obj->trunnel_error_code_) + return "A set function failed on this object"; + if (! (obj->format == T_SIGNED_CHECKSUM32_ED25519_V1)) + return "Integer out of bounds"; + if (! ((obj->length >= 1 && obj->length <= 127))) + return "Integer out of bounds"; + if (TRUNNEL_DYNARRAY_LEN(&obj->identifier) != obj->length) + return "Length mismatch for identifier"; + { + const char *msg; + if (NULL != (msg = ed25519_v1_check(obj->namespace))) + return msg; + } + return NULL; +} + +ssize_t +signed_checksum32_ed25519_v1_encoded_len(const signed_checksum32_ed25519_v1_t *obj) +{ + ssize_t result = 0; + + if (NULL != signed_checksum32_ed25519_v1_check(obj)) + return -1; + + + /* Length of u64 format IN [T_SIGNED_CHECKSUM32_ED25519_V1] */ + result += 8; + + /* Length of u8 checksum[32] */ + result += 32; + + /* Length of u64 length IN [1..127] */ + result += 8; + + /* Length of u8 identifier[length] */ + result += TRUNNEL_DYNARRAY_LEN(&obj->identifier); + + /* Length of u8 signature[64] */ + result += 64; + + /* Length of struct ed25519_v1 namespace */ + result += ed25519_v1_encoded_len(obj->namespace); + return result; +} +int +signed_checksum32_ed25519_v1_clear_errors(signed_checksum32_ed25519_v1_t *obj) +{ + int r = obj->trunnel_error_code_; + obj->trunnel_error_code_ = 0; + return r; +} +ssize_t +signed_checksum32_ed25519_v1_encode(uint8_t *output, const size_t avail, const signed_checksum32_ed25519_v1_t *obj) +{ + ssize_t result = 0; + size_t written = 0; + uint8_t *ptr = output; + const char *msg; +#ifdef TRUNNEL_CHECK_ENCODED_LEN + const ssize_t encoded_len = signed_checksum32_ed25519_v1_encoded_len(obj); +#endif + + if (NULL != (msg = signed_checksum32_ed25519_v1_check(obj))) + goto check_failed; + +#ifdef TRUNNEL_CHECK_ENCODED_LEN + trunnel_assert(encoded_len >= 0); +#endif + + /* Encode u64 format IN [T_SIGNED_CHECKSUM32_ED25519_V1] */ + trunnel_assert(written <= avail); + if (avail - written < 8) + goto truncated; + trunnel_set_uint64(ptr, trunnel_htonll(obj->format)); + written += 8; ptr += 8; + + /* Encode u8 checksum[32] */ + trunnel_assert(written <= avail); + if (avail - written < 32) + goto truncated; + memcpy(ptr, obj->checksum, 32); + written += 32; ptr += 32; + + /* Encode u64 length IN [1..127] */ + trunnel_assert(written <= avail); + if (avail - written < 8) + goto truncated; + trunnel_set_uint64(ptr, trunnel_htonll(obj->length)); + written += 8; ptr += 8; + + /* Encode u8 identifier[length] */ + { + size_t elt_len = TRUNNEL_DYNARRAY_LEN(&obj->identifier); + trunnel_assert(obj->length == elt_len); + trunnel_assert(written <= avail); + if (avail - written < elt_len) + goto truncated; + if (elt_len) + memcpy(ptr, obj->identifier.elts_, elt_len); + written += elt_len; ptr += elt_len; + } + + /* Encode u8 signature[64] */ + trunnel_assert(written <= avail); + if (avail - written < 64) + goto truncated; + memcpy(ptr, obj->signature, 64); + written += 64; ptr += 64; + + /* Encode struct ed25519_v1 namespace */ + trunnel_assert(written <= avail); + result = ed25519_v1_encode(ptr, avail - written, obj->namespace); + if (result < 0) + goto fail; /* XXXXXXX !*/ + written += result; ptr += result; + + + trunnel_assert(ptr == output + written); +#ifdef TRUNNEL_CHECK_ENCODED_LEN + { + trunnel_assert(encoded_len >= 0); + trunnel_assert((size_t)encoded_len == written); + } + +#endif + + return written; + + truncated: + result = -2; + goto fail; + check_failed: + (void)msg; + result = -1; + goto fail; + fail: + trunnel_assert(result < 0); + return result; +} + +/** As signed_checksum32_ed25519_v1_parse(), but do not allocate the + * output object. + */ +static ssize_t +signed_checksum32_ed25519_v1_parse_into(signed_checksum32_ed25519_v1_t *obj, const uint8_t *input, const size_t len_in) +{ + const uint8_t *ptr = input; + size_t remaining = len_in; + ssize_t result = 0; + (void)result; + + /* Parse u64 format IN [T_SIGNED_CHECKSUM32_ED25519_V1] */ + CHECK_REMAINING(8, truncated); + obj->format = trunnel_ntohll(trunnel_get_uint64(ptr)); + remaining -= 8; ptr += 8; + if (! (obj->format == T_SIGNED_CHECKSUM32_ED25519_V1)) + goto fail; + + /* Parse u8 checksum[32] */ + CHECK_REMAINING(32, truncated); + memcpy(obj->checksum, ptr, 32); + remaining -= 32; ptr += 32; + + /* Parse u64 length IN [1..127] */ + CHECK_REMAINING(8, truncated); + obj->length = trunnel_ntohll(trunnel_get_uint64(ptr)); + remaining -= 8; ptr += 8; + if (! ((obj->length >= 1 && obj->length <= 127))) + goto fail; + + /* Parse u8 identifier[length] */ + CHECK_REMAINING(obj->length, truncated); + TRUNNEL_DYNARRAY_EXPAND(uint8_t, &obj->identifier, obj->length, {}); + obj->identifier.n_ = obj->length; + if (obj->length) + memcpy(obj->identifier.elts_, ptr, obj->length); + ptr += obj->length; remaining -= obj->length; + + /* Parse u8 signature[64] */ + CHECK_REMAINING(64, truncated); + memcpy(obj->signature, ptr, 64); + remaining -= 64; ptr += 64; + + /* Parse struct ed25519_v1 namespace */ + result = ed25519_v1_parse(&obj->namespace, ptr, remaining); + if (result < 0) + goto relay_fail; + trunnel_assert((size_t)result <= remaining); + remaining -= result; ptr += result; + trunnel_assert(ptr + remaining == input + len_in); + return len_in - remaining; + + truncated: + return -2; + relay_fail: + trunnel_assert(result < 0); + return result; + trunnel_alloc_failed: + return -1; + fail: + result = -1; + return result; +} + +ssize_t +signed_checksum32_ed25519_v1_parse(signed_checksum32_ed25519_v1_t **output, const uint8_t *input, const size_t len_in) +{ + ssize_t result; + *output = signed_checksum32_ed25519_v1_new(); + if (NULL == *output) + return -1; + result = signed_checksum32_ed25519_v1_parse_into(*output, input, len_in); + if (result < 0) { + signed_checksum32_ed25519_v1_free(*output); + *output = NULL; + } + return result; +} diff --git a/trunnel/stfe.h b/trunnel/stfe.h new file mode 100644 index 0000000..c92d1e4 --- /dev/null +++ b/trunnel/stfe.h @@ -0,0 +1,985 @@ +/* stfe.h -- generated by Trunnel v1.5.3. + * https://gitweb.torproject.org/trunnel.git + * You probably shouldn't edit this file. + */ +#ifndef TRUNNEL_STFE_H +#define TRUNNEL_STFE_H + +#include +#include "trunnel.h" + +#define T_GET_ENTRIES_V1 1 +#define T_GET_PROOF_BY_HASH_V1 2 +#define T_GET_CONSISTENCY_PROOF_V1 3 +#define T_INCLUSION_PROOF_V1 4 +#define T_CONSISTENCY_PROOF_V1 5 +#define T_SIGNED_TREE_HEAD_V1 6 +#define T_SIGNED_CHECKSUM32_ED25519_V1 7 +#define T_ED25519_V1 8 +#if !defined(TRUNNEL_OPAQUE) && !defined(TRUNNEL_OPAQUE_ED25519_V1) +struct ed25519_v1_st { + uint64_t format; + uint8_t pubkey[32]; + uint8_t trunnel_error_code_; +}; +#endif +typedef struct ed25519_v1_st ed25519_v1_t; +#if !defined(TRUNNEL_OPAQUE) && !defined(TRUNNEL_OPAQUE_REQ_GET_CONSISTENCY_PROOF_V1) +struct req_get_consistency_proof_v1_st { + uint64_t old_size; + uint64_t new_size; + uint8_t trunnel_error_code_; +}; +#endif +typedef struct req_get_consistency_proof_v1_st req_get_consistency_proof_v1_t; +#if !defined(TRUNNEL_OPAQUE) && !defined(TRUNNEL_OPAQUE_REQ_GET_ENTRIES_V1) +struct req_get_entries_v1_st { + uint64_t start_size; + uint64_t end_size; + uint8_t trunnel_error_code_; +}; +#endif +typedef struct req_get_entries_v1_st req_get_entries_v1_t; +#if !defined(TRUNNEL_OPAQUE) && !defined(TRUNNEL_OPAQUE_REQ_GET_PROOF_BY_HASH_V1) +struct req_get_proof_by_hash_v1_st { + uint64_t tree_size; + uint8_t leaf_hash[32]; + uint8_t trunnel_error_code_; +}; +#endif +typedef struct req_get_proof_by_hash_v1_st req_get_proof_by_hash_v1_t; +#if !defined(TRUNNEL_OPAQUE) && !defined(TRUNNEL_OPAQUE_SIGNED_TREE_HEAD_V1) +struct signed_tree_head_v1_st { + uint64_t format; + uint64_t timestamp; + uint64_t tree_size; + uint8_t root_hash[32]; + uint64_t length; + TRUNNEL_DYNARRAY_HEAD(, uint8_t) sigident; + uint8_t trunnel_error_code_; +}; +#endif +typedef struct signed_tree_head_v1_st signed_tree_head_v1_t; +#if !defined(TRUNNEL_OPAQUE) && !defined(TRUNNEL_OPAQUE_CONSISTENCY_PROOF_V1) +struct consistency_proof_v1_st { + uint64_t format; + struct ed25519_v1_st *identifier; + uint64_t old_size; + uint64_t new_size; + uint64_t length; + TRUNNEL_DYNARRAY_HEAD(, uint8_t) hashes; + uint8_t trunnel_error_code_; +}; +#endif +typedef struct consistency_proof_v1_st consistency_proof_v1_t; +#if !defined(TRUNNEL_OPAQUE) && !defined(TRUNNEL_OPAQUE_INCLUSION_PROOF_V1) +struct inclusion_proof_v1_st { + uint64_t format; + struct ed25519_v1_st *identifier; + uint64_t tree_size; + uint64_t leaf_index; + uint64_t length; + TRUNNEL_DYNARRAY_HEAD(, uint8_t) hashes; + uint8_t trunnel_error_code_; +}; +#endif +typedef struct inclusion_proof_v1_st inclusion_proof_v1_t; +#if !defined(TRUNNEL_OPAQUE) && !defined(TRUNNEL_OPAQUE_REQUEST_V1) +struct request_v1_st { + uint64_t format; + struct req_get_entries_v1_st *request_get_entries; + struct req_get_proof_by_hash_v1_st *request_get_proof_by_hash; + struct req_get_consistency_proof_v1_st *request_get_consistency_proof; + uint8_t trunnel_error_code_; +}; +#endif +typedef struct request_v1_st request_v1_t; +#if !defined(TRUNNEL_OPAQUE) && !defined(TRUNNEL_OPAQUE_SIGNED_CHECKSUM32_ED25519_V1) +struct signed_checksum32_ed25519_v1_st { + uint64_t format; + uint8_t checksum[32]; + uint64_t length; + TRUNNEL_DYNARRAY_HEAD(, uint8_t) identifier; + uint8_t signature[64]; + struct ed25519_v1_st *namespace; + uint8_t trunnel_error_code_; +}; +#endif +typedef struct signed_checksum32_ed25519_v1_st signed_checksum32_ed25519_v1_t; +/** Return a newly allocated ed25519_v1 with all elements set to zero. + */ +ed25519_v1_t *ed25519_v1_new(void); +/** Release all storage held by the ed25519_v1 in 'victim'. (Do + * nothing if 'victim' is NULL.) + */ +void ed25519_v1_free(ed25519_v1_t *victim); +/** Try to parse a ed25519_v1 from the buffer in 'input', using up to + * 'len_in' bytes from the input buffer. On success, return the number + * of bytes consumed and set *output to the newly allocated + * ed25519_v1_t. On failure, return -2 if the input appears truncated, + * and -1 if the input is otherwise invalid. + */ +ssize_t ed25519_v1_parse(ed25519_v1_t **output, const uint8_t *input, const size_t len_in); +/** Return the number of bytes we expect to need to encode the + * ed25519_v1 in 'obj'. On failure, return a negative value. Note that + * this value may be an overestimate, and can even be an underestimate + * for certain unencodeable objects. + */ +ssize_t ed25519_v1_encoded_len(const ed25519_v1_t *obj); +/** Try to encode the ed25519_v1 from 'input' into the buffer at + * 'output', using up to 'avail' bytes of the output buffer. On + * success, return the number of bytes used. On failure, return -2 if + * the buffer was not long enough, and -1 if the input was invalid. + */ +ssize_t ed25519_v1_encode(uint8_t *output, size_t avail, const ed25519_v1_t *input); +/** Check whether the internal state of the ed25519_v1 in 'obj' is + * consistent. Return NULL if it is, and a short message if it is not. + */ +const char *ed25519_v1_check(const ed25519_v1_t *obj); +/** Clear any errors that were set on the object 'obj' by its setter + * functions. Return true iff errors were cleared. + */ +int ed25519_v1_clear_errors(ed25519_v1_t *obj); +/** Return the value of the format field of the ed25519_v1_t in 'inp' + */ +uint64_t ed25519_v1_get_format(const ed25519_v1_t *inp); +/** Set the value of the format field of the ed25519_v1_t in 'inp' to + * 'val'. Return 0 on success; return -1 and set the error code on + * 'inp' on failure. + */ +int ed25519_v1_set_format(ed25519_v1_t *inp, uint64_t val); +/** Return the (constant) length of the array holding the pubkey field + * of the ed25519_v1_t in 'inp'. + */ +size_t ed25519_v1_getlen_pubkey(const ed25519_v1_t *inp); +/** Return the element at position 'idx' of the fixed array field + * pubkey of the ed25519_v1_t in 'inp'. + */ +uint8_t ed25519_v1_get_pubkey(ed25519_v1_t *inp, size_t idx); +/** As ed25519_v1_get_pubkey, but take and return a const pointer + */ +uint8_t ed25519_v1_getconst_pubkey(const ed25519_v1_t *inp, size_t idx); +/** Change the element at position 'idx' of the fixed array field + * pubkey of the ed25519_v1_t in 'inp', so that it will hold the value + * 'elt'. + */ +int ed25519_v1_set_pubkey(ed25519_v1_t *inp, size_t idx, uint8_t elt); +/** Return a pointer to the 32-element array field pubkey of 'inp'. + */ +uint8_t * ed25519_v1_getarray_pubkey(ed25519_v1_t *inp); +/** As ed25519_v1_get_pubkey, but take and return a const pointer + */ +const uint8_t * ed25519_v1_getconstarray_pubkey(const ed25519_v1_t *inp); +/** Return a newly allocated req_get_consistency_proof_v1 with all + * elements set to zero. + */ +req_get_consistency_proof_v1_t *req_get_consistency_proof_v1_new(void); +/** Release all storage held by the req_get_consistency_proof_v1 in + * 'victim'. (Do nothing if 'victim' is NULL.) + */ +void req_get_consistency_proof_v1_free(req_get_consistency_proof_v1_t *victim); +/** Try to parse a req_get_consistency_proof_v1 from the buffer in + * 'input', using up to 'len_in' bytes from the input buffer. On + * success, return the number of bytes consumed and set *output to the + * newly allocated req_get_consistency_proof_v1_t. On failure, return + * -2 if the input appears truncated, and -1 if the input is otherwise + * invalid. + */ +ssize_t req_get_consistency_proof_v1_parse(req_get_consistency_proof_v1_t **output, const uint8_t *input, const size_t len_in); +/** Return the number of bytes we expect to need to encode the + * req_get_consistency_proof_v1 in 'obj'. On failure, return a + * negative value. Note that this value may be an overestimate, and + * can even be an underestimate for certain unencodeable objects. + */ +ssize_t req_get_consistency_proof_v1_encoded_len(const req_get_consistency_proof_v1_t *obj); +/** Try to encode the req_get_consistency_proof_v1 from 'input' into + * the buffer at 'output', using up to 'avail' bytes of the output + * buffer. On success, return the number of bytes used. On failure, + * return -2 if the buffer was not long enough, and -1 if the input + * was invalid. + */ +ssize_t req_get_consistency_proof_v1_encode(uint8_t *output, size_t avail, const req_get_consistency_proof_v1_t *input); +/** Check whether the internal state of the + * req_get_consistency_proof_v1 in 'obj' is consistent. Return NULL if + * it is, and a short message if it is not. + */ +const char *req_get_consistency_proof_v1_check(const req_get_consistency_proof_v1_t *obj); +/** Clear any errors that were set on the object 'obj' by its setter + * functions. Return true iff errors were cleared. + */ +int req_get_consistency_proof_v1_clear_errors(req_get_consistency_proof_v1_t *obj); +/** Return the value of the old_size field of the + * req_get_consistency_proof_v1_t in 'inp' + */ +uint64_t req_get_consistency_proof_v1_get_old_size(const req_get_consistency_proof_v1_t *inp); +/** Set the value of the old_size field of the + * req_get_consistency_proof_v1_t in 'inp' to 'val'. Return 0 on + * success; return -1 and set the error code on 'inp' on failure. + */ +int req_get_consistency_proof_v1_set_old_size(req_get_consistency_proof_v1_t *inp, uint64_t val); +/** Return the value of the new_size field of the + * req_get_consistency_proof_v1_t in 'inp' + */ +uint64_t req_get_consistency_proof_v1_get_new_size(const req_get_consistency_proof_v1_t *inp); +/** Set the value of the new_size field of the + * req_get_consistency_proof_v1_t in 'inp' to 'val'. Return 0 on + * success; return -1 and set the error code on 'inp' on failure. + */ +int req_get_consistency_proof_v1_set_new_size(req_get_consistency_proof_v1_t *inp, uint64_t val); +/** Return a newly allocated req_get_entries_v1 with all elements set + * to zero. + */ +req_get_entries_v1_t *req_get_entries_v1_new(void); +/** Release all storage held by the req_get_entries_v1 in 'victim'. + * (Do nothing if 'victim' is NULL.) + */ +void req_get_entries_v1_free(req_get_entries_v1_t *victim); +/** Try to parse a req_get_entries_v1 from the buffer in 'input', + * using up to 'len_in' bytes from the input buffer. On success, + * return the number of bytes consumed and set *output to the newly + * allocated req_get_entries_v1_t. On failure, return -2 if the input + * appears truncated, and -1 if the input is otherwise invalid. + */ +ssize_t req_get_entries_v1_parse(req_get_entries_v1_t **output, const uint8_t *input, const size_t len_in); +/** Return the number of bytes we expect to need to encode the + * req_get_entries_v1 in 'obj'. On failure, return a negative value. + * Note that this value may be an overestimate, and can even be an + * underestimate for certain unencodeable objects. + */ +ssize_t req_get_entries_v1_encoded_len(const req_get_entries_v1_t *obj); +/** Try to encode the req_get_entries_v1 from 'input' into the buffer + * at 'output', using up to 'avail' bytes of the output buffer. On + * success, return the number of bytes used. On failure, return -2 if + * the buffer was not long enough, and -1 if the input was invalid. + */ +ssize_t req_get_entries_v1_encode(uint8_t *output, size_t avail, const req_get_entries_v1_t *input); +/** Check whether the internal state of the req_get_entries_v1 in + * 'obj' is consistent. Return NULL if it is, and a short message if + * it is not. + */ +const char *req_get_entries_v1_check(const req_get_entries_v1_t *obj); +/** Clear any errors that were set on the object 'obj' by its setter + * functions. Return true iff errors were cleared. + */ +int req_get_entries_v1_clear_errors(req_get_entries_v1_t *obj); +/** Return the value of the start_size field of the + * req_get_entries_v1_t in 'inp' + */ +uint64_t req_get_entries_v1_get_start_size(const req_get_entries_v1_t *inp); +/** Set the value of the start_size field of the req_get_entries_v1_t + * in 'inp' to 'val'. Return 0 on success; return -1 and set the error + * code on 'inp' on failure. + */ +int req_get_entries_v1_set_start_size(req_get_entries_v1_t *inp, uint64_t val); +/** Return the value of the end_size field of the req_get_entries_v1_t + * in 'inp' + */ +uint64_t req_get_entries_v1_get_end_size(const req_get_entries_v1_t *inp); +/** Set the value of the end_size field of the req_get_entries_v1_t in + * 'inp' to 'val'. Return 0 on success; return -1 and set the error + * code on 'inp' on failure. + */ +int req_get_entries_v1_set_end_size(req_get_entries_v1_t *inp, uint64_t val); +/** Return a newly allocated req_get_proof_by_hash_v1 with all + * elements set to zero. + */ +req_get_proof_by_hash_v1_t *req_get_proof_by_hash_v1_new(void); +/** Release all storage held by the req_get_proof_by_hash_v1 in + * 'victim'. (Do nothing if 'victim' is NULL.) + */ +void req_get_proof_by_hash_v1_free(req_get_proof_by_hash_v1_t *victim); +/** Try to parse a req_get_proof_by_hash_v1 from the buffer in + * 'input', using up to 'len_in' bytes from the input buffer. On + * success, return the number of bytes consumed and set *output to the + * newly allocated req_get_proof_by_hash_v1_t. On failure, return -2 + * if the input appears truncated, and -1 if the input is otherwise + * invalid. + */ +ssize_t req_get_proof_by_hash_v1_parse(req_get_proof_by_hash_v1_t **output, const uint8_t *input, const size_t len_in); +/** Return the number of bytes we expect to need to encode the + * req_get_proof_by_hash_v1 in 'obj'. On failure, return a negative + * value. Note that this value may be an overestimate, and can even be + * an underestimate for certain unencodeable objects. + */ +ssize_t req_get_proof_by_hash_v1_encoded_len(const req_get_proof_by_hash_v1_t *obj); +/** Try to encode the req_get_proof_by_hash_v1 from 'input' into the + * buffer at 'output', using up to 'avail' bytes of the output buffer. + * On success, return the number of bytes used. On failure, return -2 + * if the buffer was not long enough, and -1 if the input was invalid. + */ +ssize_t req_get_proof_by_hash_v1_encode(uint8_t *output, size_t avail, const req_get_proof_by_hash_v1_t *input); +/** Check whether the internal state of the req_get_proof_by_hash_v1 + * in 'obj' is consistent. Return NULL if it is, and a short message + * if it is not. + */ +const char *req_get_proof_by_hash_v1_check(const req_get_proof_by_hash_v1_t *obj); +/** Clear any errors that were set on the object 'obj' by its setter + * functions. Return true iff errors were cleared. + */ +int req_get_proof_by_hash_v1_clear_errors(req_get_proof_by_hash_v1_t *obj); +/** Return the value of the tree_size field of the + * req_get_proof_by_hash_v1_t in 'inp' + */ +uint64_t req_get_proof_by_hash_v1_get_tree_size(const req_get_proof_by_hash_v1_t *inp); +/** Set the value of the tree_size field of the + * req_get_proof_by_hash_v1_t in 'inp' to 'val'. Return 0 on success; + * return -1 and set the error code on 'inp' on failure. + */ +int req_get_proof_by_hash_v1_set_tree_size(req_get_proof_by_hash_v1_t *inp, uint64_t val); +/** Return the (constant) length of the array holding the leaf_hash + * field of the req_get_proof_by_hash_v1_t in 'inp'. + */ +size_t req_get_proof_by_hash_v1_getlen_leaf_hash(const req_get_proof_by_hash_v1_t *inp); +/** Return the element at position 'idx' of the fixed array field + * leaf_hash of the req_get_proof_by_hash_v1_t in 'inp'. + */ +uint8_t req_get_proof_by_hash_v1_get_leaf_hash(req_get_proof_by_hash_v1_t *inp, size_t idx); +/** As req_get_proof_by_hash_v1_get_leaf_hash, but take and return a + * const pointer + */ +uint8_t req_get_proof_by_hash_v1_getconst_leaf_hash(const req_get_proof_by_hash_v1_t *inp, size_t idx); +/** Change the element at position 'idx' of the fixed array field + * leaf_hash of the req_get_proof_by_hash_v1_t in 'inp', so that it + * will hold the value 'elt'. + */ +int req_get_proof_by_hash_v1_set_leaf_hash(req_get_proof_by_hash_v1_t *inp, size_t idx, uint8_t elt); +/** Return a pointer to the 32-element array field leaf_hash of 'inp'. + */ +uint8_t * req_get_proof_by_hash_v1_getarray_leaf_hash(req_get_proof_by_hash_v1_t *inp); +/** As req_get_proof_by_hash_v1_get_leaf_hash, but take and return a + * const pointer + */ +const uint8_t * req_get_proof_by_hash_v1_getconstarray_leaf_hash(const req_get_proof_by_hash_v1_t *inp); +/** Return a newly allocated signed_tree_head_v1 with all elements set + * to zero. + */ +signed_tree_head_v1_t *signed_tree_head_v1_new(void); +/** Release all storage held by the signed_tree_head_v1 in 'victim'. + * (Do nothing if 'victim' is NULL.) + */ +void signed_tree_head_v1_free(signed_tree_head_v1_t *victim); +/** Try to parse a signed_tree_head_v1 from the buffer in 'input', + * using up to 'len_in' bytes from the input buffer. On success, + * return the number of bytes consumed and set *output to the newly + * allocated signed_tree_head_v1_t. On failure, return -2 if the input + * appears truncated, and -1 if the input is otherwise invalid. + */ +ssize_t signed_tree_head_v1_parse(signed_tree_head_v1_t **output, const uint8_t *input, const size_t len_in); +/** Return the number of bytes we expect to need to encode the + * signed_tree_head_v1 in 'obj'. On failure, return a negative value. + * Note that this value may be an overestimate, and can even be an + * underestimate for certain unencodeable objects. + */ +ssize_t signed_tree_head_v1_encoded_len(const signed_tree_head_v1_t *obj); +/** Try to encode the signed_tree_head_v1 from 'input' into the buffer + * at 'output', using up to 'avail' bytes of the output buffer. On + * success, return the number of bytes used. On failure, return -2 if + * the buffer was not long enough, and -1 if the input was invalid. + */ +ssize_t signed_tree_head_v1_encode(uint8_t *output, size_t avail, const signed_tree_head_v1_t *input); +/** Check whether the internal state of the signed_tree_head_v1 in + * 'obj' is consistent. Return NULL if it is, and a short message if + * it is not. + */ +const char *signed_tree_head_v1_check(const signed_tree_head_v1_t *obj); +/** Clear any errors that were set on the object 'obj' by its setter + * functions. Return true iff errors were cleared. + */ +int signed_tree_head_v1_clear_errors(signed_tree_head_v1_t *obj); +/** Return the value of the format field of the signed_tree_head_v1_t + * in 'inp' + */ +uint64_t signed_tree_head_v1_get_format(const signed_tree_head_v1_t *inp); +/** Set the value of the format field of the signed_tree_head_v1_t in + * 'inp' to 'val'. Return 0 on success; return -1 and set the error + * code on 'inp' on failure. + */ +int signed_tree_head_v1_set_format(signed_tree_head_v1_t *inp, uint64_t val); +/** Return the value of the timestamp field of the + * signed_tree_head_v1_t in 'inp' + */ +uint64_t signed_tree_head_v1_get_timestamp(const signed_tree_head_v1_t *inp); +/** Set the value of the timestamp field of the signed_tree_head_v1_t + * in 'inp' to 'val'. Return 0 on success; return -1 and set the error + * code on 'inp' on failure. + */ +int signed_tree_head_v1_set_timestamp(signed_tree_head_v1_t *inp, uint64_t val); +/** Return the value of the tree_size field of the + * signed_tree_head_v1_t in 'inp' + */ +uint64_t signed_tree_head_v1_get_tree_size(const signed_tree_head_v1_t *inp); +/** Set the value of the tree_size field of the signed_tree_head_v1_t + * in 'inp' to 'val'. Return 0 on success; return -1 and set the error + * code on 'inp' on failure. + */ +int signed_tree_head_v1_set_tree_size(signed_tree_head_v1_t *inp, uint64_t val); +/** Return the (constant) length of the array holding the root_hash + * field of the signed_tree_head_v1_t in 'inp'. + */ +size_t signed_tree_head_v1_getlen_root_hash(const signed_tree_head_v1_t *inp); +/** Return the element at position 'idx' of the fixed array field + * root_hash of the signed_tree_head_v1_t in 'inp'. + */ +uint8_t signed_tree_head_v1_get_root_hash(signed_tree_head_v1_t *inp, size_t idx); +/** As signed_tree_head_v1_get_root_hash, but take and return a const + * pointer + */ +uint8_t signed_tree_head_v1_getconst_root_hash(const signed_tree_head_v1_t *inp, size_t idx); +/** Change the element at position 'idx' of the fixed array field + * root_hash of the signed_tree_head_v1_t in 'inp', so that it will + * hold the value 'elt'. + */ +int signed_tree_head_v1_set_root_hash(signed_tree_head_v1_t *inp, size_t idx, uint8_t elt); +/** Return a pointer to the 32-element array field root_hash of 'inp'. + */ +uint8_t * signed_tree_head_v1_getarray_root_hash(signed_tree_head_v1_t *inp); +/** As signed_tree_head_v1_get_root_hash, but take and return a const + * pointer + */ +const uint8_t * signed_tree_head_v1_getconstarray_root_hash(const signed_tree_head_v1_t *inp); +/** Return the value of the length field of the signed_tree_head_v1_t + * in 'inp' + */ +uint64_t signed_tree_head_v1_get_length(const signed_tree_head_v1_t *inp); +/** Set the value of the length field of the signed_tree_head_v1_t in + * 'inp' to 'val'. Return 0 on success; return -1 and set the error + * code on 'inp' on failure. + */ +int signed_tree_head_v1_set_length(signed_tree_head_v1_t *inp, uint64_t val); +/** Return the length of the dynamic array holding the sigident field + * of the signed_tree_head_v1_t in 'inp'. + */ +size_t signed_tree_head_v1_getlen_sigident(const signed_tree_head_v1_t *inp); +/** Return the element at position 'idx' of the dynamic array field + * sigident of the signed_tree_head_v1_t in 'inp'. + */ +uint8_t signed_tree_head_v1_get_sigident(signed_tree_head_v1_t *inp, size_t idx); +/** As signed_tree_head_v1_get_sigident, but take and return a const + * pointer + */ +uint8_t signed_tree_head_v1_getconst_sigident(const signed_tree_head_v1_t *inp, size_t idx); +/** Change the element at position 'idx' of the dynamic array field + * sigident of the signed_tree_head_v1_t in 'inp', so that it will + * hold the value 'elt'. + */ +int signed_tree_head_v1_set_sigident(signed_tree_head_v1_t *inp, size_t idx, uint8_t elt); +/** Append a new element 'elt' to the dynamic array field sigident of + * the signed_tree_head_v1_t in 'inp'. + */ +int signed_tree_head_v1_add_sigident(signed_tree_head_v1_t *inp, uint8_t elt); +/** Return a pointer to the variable-length array field sigident of + * 'inp'. + */ +uint8_t * signed_tree_head_v1_getarray_sigident(signed_tree_head_v1_t *inp); +/** As signed_tree_head_v1_get_sigident, but take and return a const + * pointer + */ +const uint8_t * signed_tree_head_v1_getconstarray_sigident(const signed_tree_head_v1_t *inp); +/** Change the length of the variable-length array field sigident of + * 'inp' to 'newlen'.Fill extra elements with 0. Return 0 on success; + * return -1 and set the error code on 'inp' on failure. + */ +int signed_tree_head_v1_setlen_sigident(signed_tree_head_v1_t *inp, size_t newlen); +/** Return a newly allocated consistency_proof_v1 with all elements + * set to zero. + */ +consistency_proof_v1_t *consistency_proof_v1_new(void); +/** Release all storage held by the consistency_proof_v1 in 'victim'. + * (Do nothing if 'victim' is NULL.) + */ +void consistency_proof_v1_free(consistency_proof_v1_t *victim); +/** Try to parse a consistency_proof_v1 from the buffer in 'input', + * using up to 'len_in' bytes from the input buffer. On success, + * return the number of bytes consumed and set *output to the newly + * allocated consistency_proof_v1_t. On failure, return -2 if the + * input appears truncated, and -1 if the input is otherwise invalid. + */ +ssize_t consistency_proof_v1_parse(consistency_proof_v1_t **output, const uint8_t *input, const size_t len_in); +/** Return the number of bytes we expect to need to encode the + * consistency_proof_v1 in 'obj'. On failure, return a negative value. + * Note that this value may be an overestimate, and can even be an + * underestimate for certain unencodeable objects. + */ +ssize_t consistency_proof_v1_encoded_len(const consistency_proof_v1_t *obj); +/** Try to encode the consistency_proof_v1 from 'input' into the + * buffer at 'output', using up to 'avail' bytes of the output buffer. + * On success, return the number of bytes used. On failure, return -2 + * if the buffer was not long enough, and -1 if the input was invalid. + */ +ssize_t consistency_proof_v1_encode(uint8_t *output, size_t avail, const consistency_proof_v1_t *input); +/** Check whether the internal state of the consistency_proof_v1 in + * 'obj' is consistent. Return NULL if it is, and a short message if + * it is not. + */ +const char *consistency_proof_v1_check(const consistency_proof_v1_t *obj); +/** Clear any errors that were set on the object 'obj' by its setter + * functions. Return true iff errors were cleared. + */ +int consistency_proof_v1_clear_errors(consistency_proof_v1_t *obj); +/** Return the value of the format field of the consistency_proof_v1_t + * in 'inp' + */ +uint64_t consistency_proof_v1_get_format(const consistency_proof_v1_t *inp); +/** Set the value of the format field of the consistency_proof_v1_t in + * 'inp' to 'val'. Return 0 on success; return -1 and set the error + * code on 'inp' on failure. + */ +int consistency_proof_v1_set_format(consistency_proof_v1_t *inp, uint64_t val); +/** Return the value of the identifier field of the + * consistency_proof_v1_t in 'inp' + */ +struct ed25519_v1_st * consistency_proof_v1_get_identifier(consistency_proof_v1_t *inp); +/** As consistency_proof_v1_get_identifier, but take and return a + * const pointer + */ +const struct ed25519_v1_st * consistency_proof_v1_getconst_identifier(const consistency_proof_v1_t *inp); +/** Set the value of the identifier field of the + * consistency_proof_v1_t in 'inp' to 'val'. Free the old value if + * any. Steals the referenceto 'val'.Return 0 on success; return -1 + * and set the error code on 'inp' on failure. + */ +int consistency_proof_v1_set_identifier(consistency_proof_v1_t *inp, struct ed25519_v1_st *val); +/** As consistency_proof_v1_set_identifier, but does not free the + * previous value. + */ +int consistency_proof_v1_set0_identifier(consistency_proof_v1_t *inp, struct ed25519_v1_st *val); +/** Return the value of the old_size field of the + * consistency_proof_v1_t in 'inp' + */ +uint64_t consistency_proof_v1_get_old_size(const consistency_proof_v1_t *inp); +/** Set the value of the old_size field of the consistency_proof_v1_t + * in 'inp' to 'val'. Return 0 on success; return -1 and set the error + * code on 'inp' on failure. + */ +int consistency_proof_v1_set_old_size(consistency_proof_v1_t *inp, uint64_t val); +/** Return the value of the new_size field of the + * consistency_proof_v1_t in 'inp' + */ +uint64_t consistency_proof_v1_get_new_size(const consistency_proof_v1_t *inp); +/** Set the value of the new_size field of the consistency_proof_v1_t + * in 'inp' to 'val'. Return 0 on success; return -1 and set the error + * code on 'inp' on failure. + */ +int consistency_proof_v1_set_new_size(consistency_proof_v1_t *inp, uint64_t val); +/** Return the value of the length field of the consistency_proof_v1_t + * in 'inp' + */ +uint64_t consistency_proof_v1_get_length(const consistency_proof_v1_t *inp); +/** Set the value of the length field of the consistency_proof_v1_t in + * 'inp' to 'val'. Return 0 on success; return -1 and set the error + * code on 'inp' on failure. + */ +int consistency_proof_v1_set_length(consistency_proof_v1_t *inp, uint64_t val); +/** Return the length of the dynamic array holding the hashes field of + * the consistency_proof_v1_t in 'inp'. + */ +size_t consistency_proof_v1_getlen_hashes(const consistency_proof_v1_t *inp); +/** Return the element at position 'idx' of the dynamic array field + * hashes of the consistency_proof_v1_t in 'inp'. + */ +uint8_t consistency_proof_v1_get_hashes(consistency_proof_v1_t *inp, size_t idx); +/** As consistency_proof_v1_get_hashes, but take and return a const + * pointer + */ +uint8_t consistency_proof_v1_getconst_hashes(const consistency_proof_v1_t *inp, size_t idx); +/** Change the element at position 'idx' of the dynamic array field + * hashes of the consistency_proof_v1_t in 'inp', so that it will hold + * the value 'elt'. + */ +int consistency_proof_v1_set_hashes(consistency_proof_v1_t *inp, size_t idx, uint8_t elt); +/** Append a new element 'elt' to the dynamic array field hashes of + * the consistency_proof_v1_t in 'inp'. + */ +int consistency_proof_v1_add_hashes(consistency_proof_v1_t *inp, uint8_t elt); +/** Return a pointer to the variable-length array field hashes of + * 'inp'. + */ +uint8_t * consistency_proof_v1_getarray_hashes(consistency_proof_v1_t *inp); +/** As consistency_proof_v1_get_hashes, but take and return a const + * pointer + */ +const uint8_t * consistency_proof_v1_getconstarray_hashes(const consistency_proof_v1_t *inp); +/** Change the length of the variable-length array field hashes of + * 'inp' to 'newlen'.Fill extra elements with 0. Return 0 on success; + * return -1 and set the error code on 'inp' on failure. + */ +int consistency_proof_v1_setlen_hashes(consistency_proof_v1_t *inp, size_t newlen); +/** Return a newly allocated inclusion_proof_v1 with all elements set + * to zero. + */ +inclusion_proof_v1_t *inclusion_proof_v1_new(void); +/** Release all storage held by the inclusion_proof_v1 in 'victim'. + * (Do nothing if 'victim' is NULL.) + */ +void inclusion_proof_v1_free(inclusion_proof_v1_t *victim); +/** Try to parse a inclusion_proof_v1 from the buffer in 'input', + * using up to 'len_in' bytes from the input buffer. On success, + * return the number of bytes consumed and set *output to the newly + * allocated inclusion_proof_v1_t. On failure, return -2 if the input + * appears truncated, and -1 if the input is otherwise invalid. + */ +ssize_t inclusion_proof_v1_parse(inclusion_proof_v1_t **output, const uint8_t *input, const size_t len_in); +/** Return the number of bytes we expect to need to encode the + * inclusion_proof_v1 in 'obj'. On failure, return a negative value. + * Note that this value may be an overestimate, and can even be an + * underestimate for certain unencodeable objects. + */ +ssize_t inclusion_proof_v1_encoded_len(const inclusion_proof_v1_t *obj); +/** Try to encode the inclusion_proof_v1 from 'input' into the buffer + * at 'output', using up to 'avail' bytes of the output buffer. On + * success, return the number of bytes used. On failure, return -2 if + * the buffer was not long enough, and -1 if the input was invalid. + */ +ssize_t inclusion_proof_v1_encode(uint8_t *output, size_t avail, const inclusion_proof_v1_t *input); +/** Check whether the internal state of the inclusion_proof_v1 in + * 'obj' is consistent. Return NULL if it is, and a short message if + * it is not. + */ +const char *inclusion_proof_v1_check(const inclusion_proof_v1_t *obj); +/** Clear any errors that were set on the object 'obj' by its setter + * functions. Return true iff errors were cleared. + */ +int inclusion_proof_v1_clear_errors(inclusion_proof_v1_t *obj); +/** Return the value of the format field of the inclusion_proof_v1_t + * in 'inp' + */ +uint64_t inclusion_proof_v1_get_format(const inclusion_proof_v1_t *inp); +/** Set the value of the format field of the inclusion_proof_v1_t in + * 'inp' to 'val'. Return 0 on success; return -1 and set the error + * code on 'inp' on failure. + */ +int inclusion_proof_v1_set_format(inclusion_proof_v1_t *inp, uint64_t val); +/** Return the value of the identifier field of the + * inclusion_proof_v1_t in 'inp' + */ +struct ed25519_v1_st * inclusion_proof_v1_get_identifier(inclusion_proof_v1_t *inp); +/** As inclusion_proof_v1_get_identifier, but take and return a const + * pointer + */ +const struct ed25519_v1_st * inclusion_proof_v1_getconst_identifier(const inclusion_proof_v1_t *inp); +/** Set the value of the identifier field of the inclusion_proof_v1_t + * in 'inp' to 'val'. Free the old value if any. Steals the + * referenceto 'val'.Return 0 on success; return -1 and set the error + * code on 'inp' on failure. + */ +int inclusion_proof_v1_set_identifier(inclusion_proof_v1_t *inp, struct ed25519_v1_st *val); +/** As inclusion_proof_v1_set_identifier, but does not free the + * previous value. + */ +int inclusion_proof_v1_set0_identifier(inclusion_proof_v1_t *inp, struct ed25519_v1_st *val); +/** Return the value of the tree_size field of the + * inclusion_proof_v1_t in 'inp' + */ +uint64_t inclusion_proof_v1_get_tree_size(const inclusion_proof_v1_t *inp); +/** Set the value of the tree_size field of the inclusion_proof_v1_t + * in 'inp' to 'val'. Return 0 on success; return -1 and set the error + * code on 'inp' on failure. + */ +int inclusion_proof_v1_set_tree_size(inclusion_proof_v1_t *inp, uint64_t val); +/** Return the value of the leaf_index field of the + * inclusion_proof_v1_t in 'inp' + */ +uint64_t inclusion_proof_v1_get_leaf_index(const inclusion_proof_v1_t *inp); +/** Set the value of the leaf_index field of the inclusion_proof_v1_t + * in 'inp' to 'val'. Return 0 on success; return -1 and set the error + * code on 'inp' on failure. + */ +int inclusion_proof_v1_set_leaf_index(inclusion_proof_v1_t *inp, uint64_t val); +/** Return the value of the length field of the inclusion_proof_v1_t + * in 'inp' + */ +uint64_t inclusion_proof_v1_get_length(const inclusion_proof_v1_t *inp); +/** Set the value of the length field of the inclusion_proof_v1_t in + * 'inp' to 'val'. Return 0 on success; return -1 and set the error + * code on 'inp' on failure. + */ +int inclusion_proof_v1_set_length(inclusion_proof_v1_t *inp, uint64_t val); +/** Return the length of the dynamic array holding the hashes field of + * the inclusion_proof_v1_t in 'inp'. + */ +size_t inclusion_proof_v1_getlen_hashes(const inclusion_proof_v1_t *inp); +/** Return the element at position 'idx' of the dynamic array field + * hashes of the inclusion_proof_v1_t in 'inp'. + */ +uint8_t inclusion_proof_v1_get_hashes(inclusion_proof_v1_t *inp, size_t idx); +/** As inclusion_proof_v1_get_hashes, but take and return a const + * pointer + */ +uint8_t inclusion_proof_v1_getconst_hashes(const inclusion_proof_v1_t *inp, size_t idx); +/** Change the element at position 'idx' of the dynamic array field + * hashes of the inclusion_proof_v1_t in 'inp', so that it will hold + * the value 'elt'. + */ +int inclusion_proof_v1_set_hashes(inclusion_proof_v1_t *inp, size_t idx, uint8_t elt); +/** Append a new element 'elt' to the dynamic array field hashes of + * the inclusion_proof_v1_t in 'inp'. + */ +int inclusion_proof_v1_add_hashes(inclusion_proof_v1_t *inp, uint8_t elt); +/** Return a pointer to the variable-length array field hashes of + * 'inp'. + */ +uint8_t * inclusion_proof_v1_getarray_hashes(inclusion_proof_v1_t *inp); +/** As inclusion_proof_v1_get_hashes, but take and return a const + * pointer + */ +const uint8_t * inclusion_proof_v1_getconstarray_hashes(const inclusion_proof_v1_t *inp); +/** Change the length of the variable-length array field hashes of + * 'inp' to 'newlen'.Fill extra elements with 0. Return 0 on success; + * return -1 and set the error code on 'inp' on failure. + */ +int inclusion_proof_v1_setlen_hashes(inclusion_proof_v1_t *inp, size_t newlen); +/** Return a newly allocated request_v1 with all elements set to zero. + */ +request_v1_t *request_v1_new(void); +/** Release all storage held by the request_v1 in 'victim'. (Do + * nothing if 'victim' is NULL.) + */ +void request_v1_free(request_v1_t *victim); +/** Try to parse a request_v1 from the buffer in 'input', using up to + * 'len_in' bytes from the input buffer. On success, return the number + * of bytes consumed and set *output to the newly allocated + * request_v1_t. On failure, return -2 if the input appears truncated, + * and -1 if the input is otherwise invalid. + */ +ssize_t request_v1_parse(request_v1_t **output, const uint8_t *input, const size_t len_in); +/** Return the number of bytes we expect to need to encode the + * request_v1 in 'obj'. On failure, return a negative value. Note that + * this value may be an overestimate, and can even be an underestimate + * for certain unencodeable objects. + */ +ssize_t request_v1_encoded_len(const request_v1_t *obj); +/** Try to encode the request_v1 from 'input' into the buffer at + * 'output', using up to 'avail' bytes of the output buffer. On + * success, return the number of bytes used. On failure, return -2 if + * the buffer was not long enough, and -1 if the input was invalid. + */ +ssize_t request_v1_encode(uint8_t *output, size_t avail, const request_v1_t *input); +/** Check whether the internal state of the request_v1 in 'obj' is + * consistent. Return NULL if it is, and a short message if it is not. + */ +const char *request_v1_check(const request_v1_t *obj); +/** Clear any errors that were set on the object 'obj' by its setter + * functions. Return true iff errors were cleared. + */ +int request_v1_clear_errors(request_v1_t *obj); +/** Return the value of the format field of the request_v1_t in 'inp' + */ +uint64_t request_v1_get_format(const request_v1_t *inp); +/** Set the value of the format field of the request_v1_t in 'inp' to + * 'val'. Return 0 on success; return -1 and set the error code on + * 'inp' on failure. + */ +int request_v1_set_format(request_v1_t *inp, uint64_t val); +/** Return the value of the request_get_entries field of the + * request_v1_t in 'inp' + */ +struct req_get_entries_v1_st * request_v1_get_request_get_entries(request_v1_t *inp); +/** As request_v1_get_request_get_entries, but take and return a const + * pointer + */ +const struct req_get_entries_v1_st * request_v1_getconst_request_get_entries(const request_v1_t *inp); +/** Set the value of the request_get_entries field of the request_v1_t + * in 'inp' to 'val'. Free the old value if any. Steals the + * referenceto 'val'.Return 0 on success; return -1 and set the error + * code on 'inp' on failure. + */ +int request_v1_set_request_get_entries(request_v1_t *inp, struct req_get_entries_v1_st *val); +/** As request_v1_set_request_get_entries, but does not free the + * previous value. + */ +int request_v1_set0_request_get_entries(request_v1_t *inp, struct req_get_entries_v1_st *val); +/** Return the value of the request_get_proof_by_hash field of the + * request_v1_t in 'inp' + */ +struct req_get_proof_by_hash_v1_st * request_v1_get_request_get_proof_by_hash(request_v1_t *inp); +/** As request_v1_get_request_get_proof_by_hash, but take and return a + * const pointer + */ +const struct req_get_proof_by_hash_v1_st * request_v1_getconst_request_get_proof_by_hash(const request_v1_t *inp); +/** Set the value of the request_get_proof_by_hash field of the + * request_v1_t in 'inp' to 'val'. Free the old value if any. Steals + * the referenceto 'val'.Return 0 on success; return -1 and set the + * error code on 'inp' on failure. + */ +int request_v1_set_request_get_proof_by_hash(request_v1_t *inp, struct req_get_proof_by_hash_v1_st *val); +/** As request_v1_set_request_get_proof_by_hash, but does not free the + * previous value. + */ +int request_v1_set0_request_get_proof_by_hash(request_v1_t *inp, struct req_get_proof_by_hash_v1_st *val); +/** Return the value of the request_get_consistency_proof field of the + * request_v1_t in 'inp' + */ +struct req_get_consistency_proof_v1_st * request_v1_get_request_get_consistency_proof(request_v1_t *inp); +/** As request_v1_get_request_get_consistency_proof, but take and + * return a const pointer + */ +const struct req_get_consistency_proof_v1_st * request_v1_getconst_request_get_consistency_proof(const request_v1_t *inp); +/** Set the value of the request_get_consistency_proof field of the + * request_v1_t in 'inp' to 'val'. Free the old value if any. Steals + * the referenceto 'val'.Return 0 on success; return -1 and set the + * error code on 'inp' on failure. + */ +int request_v1_set_request_get_consistency_proof(request_v1_t *inp, struct req_get_consistency_proof_v1_st *val); +/** As request_v1_set_request_get_consistency_proof, but does not free + * the previous value. + */ +int request_v1_set0_request_get_consistency_proof(request_v1_t *inp, struct req_get_consistency_proof_v1_st *val); +/** Return a newly allocated signed_checksum32_ed25519_v1 with all + * elements set to zero. + */ +signed_checksum32_ed25519_v1_t *signed_checksum32_ed25519_v1_new(void); +/** Release all storage held by the signed_checksum32_ed25519_v1 in + * 'victim'. (Do nothing if 'victim' is NULL.) + */ +void signed_checksum32_ed25519_v1_free(signed_checksum32_ed25519_v1_t *victim); +/** Try to parse a signed_checksum32_ed25519_v1 from the buffer in + * 'input', using up to 'len_in' bytes from the input buffer. On + * success, return the number of bytes consumed and set *output to the + * newly allocated signed_checksum32_ed25519_v1_t. On failure, return + * -2 if the input appears truncated, and -1 if the input is otherwise + * invalid. + */ +ssize_t signed_checksum32_ed25519_v1_parse(signed_checksum32_ed25519_v1_t **output, const uint8_t *input, const size_t len_in); +/** Return the number of bytes we expect to need to encode the + * signed_checksum32_ed25519_v1 in 'obj'. On failure, return a + * negative value. Note that this value may be an overestimate, and + * can even be an underestimate for certain unencodeable objects. + */ +ssize_t signed_checksum32_ed25519_v1_encoded_len(const signed_checksum32_ed25519_v1_t *obj); +/** Try to encode the signed_checksum32_ed25519_v1 from 'input' into + * the buffer at 'output', using up to 'avail' bytes of the output + * buffer. On success, return the number of bytes used. On failure, + * return -2 if the buffer was not long enough, and -1 if the input + * was invalid. + */ +ssize_t signed_checksum32_ed25519_v1_encode(uint8_t *output, size_t avail, const signed_checksum32_ed25519_v1_t *input); +/** Check whether the internal state of the + * signed_checksum32_ed25519_v1 in 'obj' is consistent. Return NULL if + * it is, and a short message if it is not. + */ +const char *signed_checksum32_ed25519_v1_check(const signed_checksum32_ed25519_v1_t *obj); +/** Clear any errors that were set on the object 'obj' by its setter + * functions. Return true iff errors were cleared. + */ +int signed_checksum32_ed25519_v1_clear_errors(signed_checksum32_ed25519_v1_t *obj); +/** Return the value of the format field of the + * signed_checksum32_ed25519_v1_t in 'inp' + */ +uint64_t signed_checksum32_ed25519_v1_get_format(const signed_checksum32_ed25519_v1_t *inp); +/** Set the value of the format field of the + * signed_checksum32_ed25519_v1_t in 'inp' to 'val'. Return 0 on + * success; return -1 and set the error code on 'inp' on failure. + */ +int signed_checksum32_ed25519_v1_set_format(signed_checksum32_ed25519_v1_t *inp, uint64_t val); +/** Return the (constant) length of the array holding the checksum + * field of the signed_checksum32_ed25519_v1_t in 'inp'. + */ +size_t signed_checksum32_ed25519_v1_getlen_checksum(const signed_checksum32_ed25519_v1_t *inp); +/** Return the element at position 'idx' of the fixed array field + * checksum of the signed_checksum32_ed25519_v1_t in 'inp'. + */ +uint8_t signed_checksum32_ed25519_v1_get_checksum(signed_checksum32_ed25519_v1_t *inp, size_t idx); +/** As signed_checksum32_ed25519_v1_get_checksum, but take and return + * a const pointer + */ +uint8_t signed_checksum32_ed25519_v1_getconst_checksum(const signed_checksum32_ed25519_v1_t *inp, size_t idx); +/** Change the element at position 'idx' of the fixed array field + * checksum of the signed_checksum32_ed25519_v1_t in 'inp', so that it + * will hold the value 'elt'. + */ +int signed_checksum32_ed25519_v1_set_checksum(signed_checksum32_ed25519_v1_t *inp, size_t idx, uint8_t elt); +/** Return a pointer to the 32-element array field checksum of 'inp'. + */ +uint8_t * signed_checksum32_ed25519_v1_getarray_checksum(signed_checksum32_ed25519_v1_t *inp); +/** As signed_checksum32_ed25519_v1_get_checksum, but take and return + * a const pointer + */ +const uint8_t * signed_checksum32_ed25519_v1_getconstarray_checksum(const signed_checksum32_ed25519_v1_t *inp); +/** Return the value of the length field of the + * signed_checksum32_ed25519_v1_t in 'inp' + */ +uint64_t signed_checksum32_ed25519_v1_get_length(const signed_checksum32_ed25519_v1_t *inp); +/** Set the value of the length field of the + * signed_checksum32_ed25519_v1_t in 'inp' to 'val'. Return 0 on + * success; return -1 and set the error code on 'inp' on failure. + */ +int signed_checksum32_ed25519_v1_set_length(signed_checksum32_ed25519_v1_t *inp, uint64_t val); +/** Return the length of the dynamic array holding the identifier + * field of the signed_checksum32_ed25519_v1_t in 'inp'. + */ +size_t signed_checksum32_ed25519_v1_getlen_identifier(const signed_checksum32_ed25519_v1_t *inp); +/** Return the element at position 'idx' of the dynamic array field + * identifier of the signed_checksum32_ed25519_v1_t in 'inp'. + */ +uint8_t signed_checksum32_ed25519_v1_get_identifier(signed_checksum32_ed25519_v1_t *inp, size_t idx); +/** As signed_checksum32_ed25519_v1_get_identifier, but take and + * return a const pointer + */ +uint8_t signed_checksum32_ed25519_v1_getconst_identifier(const signed_checksum32_ed25519_v1_t *inp, size_t idx); +/** Change the element at position 'idx' of the dynamic array field + * identifier of the signed_checksum32_ed25519_v1_t in 'inp', so that + * it will hold the value 'elt'. + */ +int signed_checksum32_ed25519_v1_set_identifier(signed_checksum32_ed25519_v1_t *inp, size_t idx, uint8_t elt); +/** Append a new element 'elt' to the dynamic array field identifier + * of the signed_checksum32_ed25519_v1_t in 'inp'. + */ +int signed_checksum32_ed25519_v1_add_identifier(signed_checksum32_ed25519_v1_t *inp, uint8_t elt); +/** Return a pointer to the variable-length array field identifier of + * 'inp'. + */ +uint8_t * signed_checksum32_ed25519_v1_getarray_identifier(signed_checksum32_ed25519_v1_t *inp); +/** As signed_checksum32_ed25519_v1_get_identifier, but take and + * return a const pointer + */ +const uint8_t * signed_checksum32_ed25519_v1_getconstarray_identifier(const signed_checksum32_ed25519_v1_t *inp); +/** Change the length of the variable-length array field identifier of + * 'inp' to 'newlen'.Fill extra elements with 0. Return 0 on success; + * return -1 and set the error code on 'inp' on failure. + */ +int signed_checksum32_ed25519_v1_setlen_identifier(signed_checksum32_ed25519_v1_t *inp, size_t newlen); +/** Return the (constant) length of the array holding the signature + * field of the signed_checksum32_ed25519_v1_t in 'inp'. + */ +size_t signed_checksum32_ed25519_v1_getlen_signature(const signed_checksum32_ed25519_v1_t *inp); +/** Return the element at position 'idx' of the fixed array field + * signature of the signed_checksum32_ed25519_v1_t in 'inp'. + */ +uint8_t signed_checksum32_ed25519_v1_get_signature(signed_checksum32_ed25519_v1_t *inp, size_t idx); +/** As signed_checksum32_ed25519_v1_get_signature, but take and return + * a const pointer + */ +uint8_t signed_checksum32_ed25519_v1_getconst_signature(const signed_checksum32_ed25519_v1_t *inp, size_t idx); +/** Change the element at position 'idx' of the fixed array field + * signature of the signed_checksum32_ed25519_v1_t in 'inp', so that + * it will hold the value 'elt'. + */ +int signed_checksum32_ed25519_v1_set_signature(signed_checksum32_ed25519_v1_t *inp, size_t idx, uint8_t elt); +/** Return a pointer to the 64-element array field signature of 'inp'. + */ +uint8_t * signed_checksum32_ed25519_v1_getarray_signature(signed_checksum32_ed25519_v1_t *inp); +/** As signed_checksum32_ed25519_v1_get_signature, but take and return + * a const pointer + */ +const uint8_t * signed_checksum32_ed25519_v1_getconstarray_signature(const signed_checksum32_ed25519_v1_t *inp); +/** Return the value of the namespace field of the + * signed_checksum32_ed25519_v1_t in 'inp' + */ +struct ed25519_v1_st * signed_checksum32_ed25519_v1_get_namespace(signed_checksum32_ed25519_v1_t *inp); +/** As signed_checksum32_ed25519_v1_get_namespace, but take and return + * a const pointer + */ +const struct ed25519_v1_st * signed_checksum32_ed25519_v1_getconst_namespace(const signed_checksum32_ed25519_v1_t *inp); +/** Set the value of the namespace field of the + * signed_checksum32_ed25519_v1_t in 'inp' to 'val'. Free the old + * value if any. Steals the referenceto 'val'.Return 0 on success; + * return -1 and set the error code on 'inp' on failure. + */ +int signed_checksum32_ed25519_v1_set_namespace(signed_checksum32_ed25519_v1_t *inp, struct ed25519_v1_st *val); +/** As signed_checksum32_ed25519_v1_set_namespace, but does not free + * the previous value. + */ +int signed_checksum32_ed25519_v1_set0_namespace(signed_checksum32_ed25519_v1_t *inp, struct ed25519_v1_st *val); + + +#endif diff --git a/trunnel/stfe.trunnel b/trunnel/stfe.trunnel new file mode 100644 index 0000000..eb82942 --- /dev/null +++ b/trunnel/stfe.trunnel @@ -0,0 +1,85 @@ +const T_GET_ENTRIES_V1 = 1; +const T_GET_PROOF_BY_HASH_V1 = 2; +const T_GET_CONSISTENCY_PROOF_V1 = 3; +const T_INCLUSION_PROOF_V1 = 4; +const T_CONSISTENCY_PROOF_V1 = 5; +const T_SIGNED_TREE_HEAD_V1 = 6; +const T_SIGNED_CHECKSUM32_ED25519_V1 = 7; +const T_ED25519_V1 = 8; + +struct req_get_entries_v1 { + u64 start_size; + u64 end_size; +}; + +struct req_get_proof_by_hash_v1 { + u64 tree_size; + u8 leaf_hash[32]; +}; + +struct req_get_consistency_proof_v1 { + u64 old_size; + u64 new_size; +}; + +struct request_v1 { + u64 format IN [ T_GET_ENTRIES_V1, T_GET_PROOF_BY_HASH_V1, T_GET_CONSISTENCY_PROOF_V1 ]; + + union request[format] { + T_GET_ENTRIES_V1: struct req_get_entries_v1 get_entries; + T_GET_PROOF_BY_HASH_V1: struct req_get_proof_by_hash_v1 get_proof_by_hash; + T_GET_CONSISTENCY_PROOF_V1: struct req_get_consistency_proof_v1 get_consistency_proof; + default: fail; + }; +} + +struct inclusion_proof_v1 { + u64 format IN [ T_INCLUSION_PROOF_V1 ]; + struct ed25519_v1 identifier; + u64 tree_size; + u64 leaf_index; + u64 length; /* TODO: constraint: multiple of 32 */ + u8 hashes[length]; +}; + +struct consistency_proof_v1 { + u64 format IN [ T_CONSISTENCY_PROOF_V1 ]; + struct ed25519_v1 identifier; + u64 old_size; + u64 new_size; + u64 length; /* TODO: constraint: multiple of 32 */ + u8 hashes[length]; +}; + +/* Not used +struct sigident { + u8 signature[64]; + struct ed25519_v1 identifier; +}; */ + +struct signed_tree_head_v1 { + u64 format IN [ T_SIGNED_TREE_HEAD_V1 ]; + u64 timestamp; + u64 tree_size; + u8 root_hash[32]; + u64 length; /* TODO: constraint: multiple of 104 */ + u8 sigident[length]; + /* Alternatively, if we would chose to replace length with n_items: + u64 n_items; + struct sigident[n_items]; */ +}; + +struct signed_checksum32_ed25519_v1 { + u64 format IN [ T_SIGNED_CHECKSUM32_ED25519_V1 ]; + u8 checksum[32]; + u64 length IN [ 1..127 ]; /* The spec contradicts itself on this point -- is it 127 or 128? */ + u8 identifier[length]; + u8 signature[64]; + struct ed25519_v1 namespace; +}; + +struct ed25519_v1 { + u64 format IN [ T_ED25519_V1 ]; + u8 pubkey[32]; +}; + -- cgit v1.2.3 From f3134997ccbb525cd09a8144ed6daeeb3245326a Mon Sep 17 00:00:00 2001 From: Linus Nordberg Date: Thu, 1 Apr 2021 12:54:48 +0200 Subject: trunnel description take two --- trunnel/stfe.c | 2236 ++++++++++++++++++++++++++++++++++---------------- trunnel/stfe.h | 930 +++++++++++++-------- trunnel/stfe.trunnel | 79 +- 3 files changed, 2144 insertions(+), 1101 deletions(-) diff --git a/trunnel/stfe.c b/trunnel/stfe.c index 58ecd66..7bf2652 100644 --- a/trunnel/stfe.c +++ b/trunnel/stfe.c @@ -28,150 +28,122 @@ int stfe_deadcode_dummy__ = 0; } \ } while (0) -ed25519_v1_t * -ed25519_v1_new(void) +hash_t * +hash_new(void) { - ed25519_v1_t *val = trunnel_calloc(1, sizeof(ed25519_v1_t)); + hash_t *val = trunnel_calloc(1, sizeof(hash_t)); if (NULL == val) return NULL; - val->format = T_ED25519_V1; return val; } /** Release all storage held inside 'obj', but do not free 'obj'. */ static void -ed25519_v1_clear(ed25519_v1_t *obj) +hash_clear(hash_t *obj) { (void) obj; } void -ed25519_v1_free(ed25519_v1_t *obj) +hash_free(hash_t *obj) { if (obj == NULL) return; - ed25519_v1_clear(obj); - trunnel_memwipe(obj, sizeof(ed25519_v1_t)); + hash_clear(obj); + trunnel_memwipe(obj, sizeof(hash_t)); trunnel_free_(obj); } -uint64_t -ed25519_v1_get_format(const ed25519_v1_t *inp) -{ - return inp->format; -} -int -ed25519_v1_set_format(ed25519_v1_t *inp, uint64_t val) -{ - if (! ((val == T_ED25519_V1))) { - TRUNNEL_SET_ERROR_CODE(inp); - return -1; - } - inp->format = val; - return 0; -} size_t -ed25519_v1_getlen_pubkey(const ed25519_v1_t *inp) +hash_getlen_hash(const hash_t *inp) { (void)inp; return 32; } uint8_t -ed25519_v1_get_pubkey(ed25519_v1_t *inp, size_t idx) +hash_get_hash(hash_t *inp, size_t idx) { trunnel_assert(idx < 32); - return inp->pubkey[idx]; + return inp->hash[idx]; } uint8_t -ed25519_v1_getconst_pubkey(const ed25519_v1_t *inp, size_t idx) +hash_getconst_hash(const hash_t *inp, size_t idx) { - return ed25519_v1_get_pubkey((ed25519_v1_t*)inp, idx); + return hash_get_hash((hash_t*)inp, idx); } int -ed25519_v1_set_pubkey(ed25519_v1_t *inp, size_t idx, uint8_t elt) +hash_set_hash(hash_t *inp, size_t idx, uint8_t elt) { trunnel_assert(idx < 32); - inp->pubkey[idx] = elt; + inp->hash[idx] = elt; return 0; } uint8_t * -ed25519_v1_getarray_pubkey(ed25519_v1_t *inp) +hash_getarray_hash(hash_t *inp) { - return inp->pubkey; + return inp->hash; } const uint8_t * -ed25519_v1_getconstarray_pubkey(const ed25519_v1_t *inp) +hash_getconstarray_hash(const hash_t *inp) { - return (const uint8_t *)ed25519_v1_getarray_pubkey((ed25519_v1_t*)inp); + return (const uint8_t *)hash_getarray_hash((hash_t*)inp); } const char * -ed25519_v1_check(const ed25519_v1_t *obj) +hash_check(const hash_t *obj) { if (obj == NULL) return "Object was NULL"; if (obj->trunnel_error_code_) return "A set function failed on this object"; - if (! (obj->format == T_ED25519_V1)) - return "Integer out of bounds"; return NULL; } ssize_t -ed25519_v1_encoded_len(const ed25519_v1_t *obj) +hash_encoded_len(const hash_t *obj) { ssize_t result = 0; - if (NULL != ed25519_v1_check(obj)) + if (NULL != hash_check(obj)) return -1; - /* Length of u64 format IN [T_ED25519_V1] */ - result += 8; - - /* Length of u8 pubkey[32] */ + /* Length of u8 hash[32] */ result += 32; return result; } int -ed25519_v1_clear_errors(ed25519_v1_t *obj) +hash_clear_errors(hash_t *obj) { int r = obj->trunnel_error_code_; obj->trunnel_error_code_ = 0; return r; } ssize_t -ed25519_v1_encode(uint8_t *output, const size_t avail, const ed25519_v1_t *obj) +hash_encode(uint8_t *output, const size_t avail, const hash_t *obj) { ssize_t result = 0; size_t written = 0; uint8_t *ptr = output; const char *msg; #ifdef TRUNNEL_CHECK_ENCODED_LEN - const ssize_t encoded_len = ed25519_v1_encoded_len(obj); + const ssize_t encoded_len = hash_encoded_len(obj); #endif - if (NULL != (msg = ed25519_v1_check(obj))) + if (NULL != (msg = hash_check(obj))) goto check_failed; #ifdef TRUNNEL_CHECK_ENCODED_LEN trunnel_assert(encoded_len >= 0); #endif - /* Encode u64 format IN [T_ED25519_V1] */ - trunnel_assert(written <= avail); - if (avail - written < 8) - goto truncated; - trunnel_set_uint64(ptr, trunnel_htonll(obj->format)); - written += 8; ptr += 8; - - /* Encode u8 pubkey[32] */ + /* Encode u8 hash[32] */ trunnel_assert(written <= avail); if (avail - written < 32) goto truncated; - memcpy(ptr, obj->pubkey, 32); + memcpy(ptr, obj->hash, 32); written += 32; ptr += 32; @@ -198,47 +170,37 @@ ed25519_v1_encode(uint8_t *output, const size_t avail, const ed25519_v1_t *obj) return result; } -/** As ed25519_v1_parse(), but do not allocate the output object. +/** As hash_parse(), but do not allocate the output object. */ static ssize_t -ed25519_v1_parse_into(ed25519_v1_t *obj, const uint8_t *input, const size_t len_in) +hash_parse_into(hash_t *obj, const uint8_t *input, const size_t len_in) { const uint8_t *ptr = input; size_t remaining = len_in; ssize_t result = 0; (void)result; - /* Parse u64 format IN [T_ED25519_V1] */ - CHECK_REMAINING(8, truncated); - obj->format = trunnel_ntohll(trunnel_get_uint64(ptr)); - remaining -= 8; ptr += 8; - if (! (obj->format == T_ED25519_V1)) - goto fail; - - /* Parse u8 pubkey[32] */ + /* Parse u8 hash[32] */ CHECK_REMAINING(32, truncated); - memcpy(obj->pubkey, ptr, 32); + memcpy(obj->hash, ptr, 32); remaining -= 32; ptr += 32; trunnel_assert(ptr + remaining == input + len_in); return len_in - remaining; truncated: return -2; - fail: - result = -1; - return result; } ssize_t -ed25519_v1_parse(ed25519_v1_t **output, const uint8_t *input, const size_t len_in) +hash_parse(hash_t **output, const uint8_t *input, const size_t len_in) { ssize_t result; - *output = ed25519_v1_new(); + *output = hash_new(); if (NULL == *output) return -1; - result = ed25519_v1_parse_into(*output, input, len_in); + result = hash_parse_into(*output, input, len_in); if (result < 0) { - ed25519_v1_free(*output); + hash_free(*output); *output = NULL; } return result; @@ -805,301 +767,170 @@ req_get_proof_by_hash_v1_parse(req_get_proof_by_hash_v1_t **output, const uint8_ } return result; } -signed_tree_head_v1_t * -signed_tree_head_v1_new(void) +sigident_ed25519_t * +sigident_ed25519_new(void) { - signed_tree_head_v1_t *val = trunnel_calloc(1, sizeof(signed_tree_head_v1_t)); + sigident_ed25519_t *val = trunnel_calloc(1, sizeof(sigident_ed25519_t)); if (NULL == val) return NULL; - val->format = T_SIGNED_TREE_HEAD_V1; return val; } /** Release all storage held inside 'obj', but do not free 'obj'. */ static void -signed_tree_head_v1_clear(signed_tree_head_v1_t *obj) +sigident_ed25519_clear(sigident_ed25519_t *obj) { (void) obj; - TRUNNEL_DYNARRAY_WIPE(&obj->sigident); - TRUNNEL_DYNARRAY_CLEAR(&obj->sigident); } void -signed_tree_head_v1_free(signed_tree_head_v1_t *obj) +sigident_ed25519_free(sigident_ed25519_t *obj) { if (obj == NULL) return; - signed_tree_head_v1_clear(obj); - trunnel_memwipe(obj, sizeof(signed_tree_head_v1_t)); + sigident_ed25519_clear(obj); + trunnel_memwipe(obj, sizeof(sigident_ed25519_t)); trunnel_free_(obj); } -uint64_t -signed_tree_head_v1_get_format(const signed_tree_head_v1_t *inp) -{ - return inp->format; -} -int -signed_tree_head_v1_set_format(signed_tree_head_v1_t *inp, uint64_t val) -{ - if (! ((val == T_SIGNED_TREE_HEAD_V1))) { - TRUNNEL_SET_ERROR_CODE(inp); - return -1; - } - inp->format = val; - return 0; -} -uint64_t -signed_tree_head_v1_get_timestamp(const signed_tree_head_v1_t *inp) -{ - return inp->timestamp; -} -int -signed_tree_head_v1_set_timestamp(signed_tree_head_v1_t *inp, uint64_t val) -{ - inp->timestamp = val; - return 0; -} -uint64_t -signed_tree_head_v1_get_tree_size(const signed_tree_head_v1_t *inp) -{ - return inp->tree_size; -} -int -signed_tree_head_v1_set_tree_size(signed_tree_head_v1_t *inp, uint64_t val) -{ - inp->tree_size = val; - return 0; -} size_t -signed_tree_head_v1_getlen_root_hash(const signed_tree_head_v1_t *inp) +sigident_ed25519_getlen_signature(const sigident_ed25519_t *inp) { - (void)inp; return 32; + (void)inp; return 64; } uint8_t -signed_tree_head_v1_get_root_hash(signed_tree_head_v1_t *inp, size_t idx) +sigident_ed25519_get_signature(sigident_ed25519_t *inp, size_t idx) { - trunnel_assert(idx < 32); - return inp->root_hash[idx]; + trunnel_assert(idx < 64); + return inp->signature[idx]; } uint8_t -signed_tree_head_v1_getconst_root_hash(const signed_tree_head_v1_t *inp, size_t idx) +sigident_ed25519_getconst_signature(const sigident_ed25519_t *inp, size_t idx) { - return signed_tree_head_v1_get_root_hash((signed_tree_head_v1_t*)inp, idx); + return sigident_ed25519_get_signature((sigident_ed25519_t*)inp, idx); } int -signed_tree_head_v1_set_root_hash(signed_tree_head_v1_t *inp, size_t idx, uint8_t elt) +sigident_ed25519_set_signature(sigident_ed25519_t *inp, size_t idx, uint8_t elt) { - trunnel_assert(idx < 32); - inp->root_hash[idx] = elt; + trunnel_assert(idx < 64); + inp->signature[idx] = elt; return 0; } uint8_t * -signed_tree_head_v1_getarray_root_hash(signed_tree_head_v1_t *inp) +sigident_ed25519_getarray_signature(sigident_ed25519_t *inp) { - return inp->root_hash; + return inp->signature; } const uint8_t * -signed_tree_head_v1_getconstarray_root_hash(const signed_tree_head_v1_t *inp) -{ - return (const uint8_t *)signed_tree_head_v1_getarray_root_hash((signed_tree_head_v1_t*)inp); -} -uint64_t -signed_tree_head_v1_get_length(const signed_tree_head_v1_t *inp) -{ - return inp->length; -} -int -signed_tree_head_v1_set_length(signed_tree_head_v1_t *inp, uint64_t val) +sigident_ed25519_getconstarray_signature(const sigident_ed25519_t *inp) { - inp->length = val; - return 0; + return (const uint8_t *)sigident_ed25519_getarray_signature((sigident_ed25519_t*)inp); } size_t -signed_tree_head_v1_getlen_sigident(const signed_tree_head_v1_t *inp) +sigident_ed25519_getlen_identifier(const sigident_ed25519_t *inp) { - return TRUNNEL_DYNARRAY_LEN(&inp->sigident); + (void)inp; return 32; } uint8_t -signed_tree_head_v1_get_sigident(signed_tree_head_v1_t *inp, size_t idx) +sigident_ed25519_get_identifier(sigident_ed25519_t *inp, size_t idx) { - return TRUNNEL_DYNARRAY_GET(&inp->sigident, idx); + trunnel_assert(idx < 32); + return inp->identifier[idx]; } uint8_t -signed_tree_head_v1_getconst_sigident(const signed_tree_head_v1_t *inp, size_t idx) -{ - return signed_tree_head_v1_get_sigident((signed_tree_head_v1_t*)inp, idx); -} -int -signed_tree_head_v1_set_sigident(signed_tree_head_v1_t *inp, size_t idx, uint8_t elt) +sigident_ed25519_getconst_identifier(const sigident_ed25519_t *inp, size_t idx) { - TRUNNEL_DYNARRAY_SET(&inp->sigident, idx, elt); - return 0; + return sigident_ed25519_get_identifier((sigident_ed25519_t*)inp, idx); } int -signed_tree_head_v1_add_sigident(signed_tree_head_v1_t *inp, uint8_t elt) +sigident_ed25519_set_identifier(sigident_ed25519_t *inp, size_t idx, uint8_t elt) { -#if SIZE_MAX >= UINT64_MAX - if (inp->sigident.n_ == UINT64_MAX) - goto trunnel_alloc_failed; -#endif - TRUNNEL_DYNARRAY_ADD(uint8_t, &inp->sigident, elt, {}); + trunnel_assert(idx < 32); + inp->identifier[idx] = elt; return 0; - trunnel_alloc_failed: - TRUNNEL_SET_ERROR_CODE(inp); - return -1; } uint8_t * -signed_tree_head_v1_getarray_sigident(signed_tree_head_v1_t *inp) +sigident_ed25519_getarray_identifier(sigident_ed25519_t *inp) { - return inp->sigident.elts_; + return inp->identifier; } const uint8_t * -signed_tree_head_v1_getconstarray_sigident(const signed_tree_head_v1_t *inp) -{ - return (const uint8_t *)signed_tree_head_v1_getarray_sigident((signed_tree_head_v1_t*)inp); -} -int -signed_tree_head_v1_setlen_sigident(signed_tree_head_v1_t *inp, size_t newlen) +sigident_ed25519_getconstarray_identifier(const sigident_ed25519_t *inp) { - uint8_t *newptr; -#if UINT64_MAX < SIZE_MAX - if (newlen > UINT64_MAX) - goto trunnel_alloc_failed; -#endif - newptr = trunnel_dynarray_setlen(&inp->sigident.allocated_, - &inp->sigident.n_, inp->sigident.elts_, newlen, - sizeof(inp->sigident.elts_[0]), (trunnel_free_fn_t) NULL, - &inp->trunnel_error_code_); - if (newlen != 0 && newptr == NULL) - goto trunnel_alloc_failed; - inp->sigident.elts_ = newptr; - return 0; - trunnel_alloc_failed: - TRUNNEL_SET_ERROR_CODE(inp); - return -1; + return (const uint8_t *)sigident_ed25519_getarray_identifier((sigident_ed25519_t*)inp); } const char * -signed_tree_head_v1_check(const signed_tree_head_v1_t *obj) +sigident_ed25519_check(const sigident_ed25519_t *obj) { if (obj == NULL) return "Object was NULL"; if (obj->trunnel_error_code_) return "A set function failed on this object"; - if (! (obj->format == T_SIGNED_TREE_HEAD_V1)) - return "Integer out of bounds"; - if (TRUNNEL_DYNARRAY_LEN(&obj->sigident) != obj->length) - return "Length mismatch for sigident"; return NULL; } ssize_t -signed_tree_head_v1_encoded_len(const signed_tree_head_v1_t *obj) +sigident_ed25519_encoded_len(const sigident_ed25519_t *obj) { ssize_t result = 0; - if (NULL != signed_tree_head_v1_check(obj)) + if (NULL != sigident_ed25519_check(obj)) return -1; - /* Length of u64 format IN [T_SIGNED_TREE_HEAD_V1] */ - result += 8; - - /* Length of u64 timestamp */ - result += 8; - - /* Length of u64 tree_size */ - result += 8; + /* Length of u8 signature[64] */ + result += 64; - /* Length of u8 root_hash[32] */ + /* Length of u8 identifier[32] */ result += 32; - - /* Length of u64 length */ - result += 8; - - /* Length of u8 sigident[length] */ - result += TRUNNEL_DYNARRAY_LEN(&obj->sigident); return result; } int -signed_tree_head_v1_clear_errors(signed_tree_head_v1_t *obj) +sigident_ed25519_clear_errors(sigident_ed25519_t *obj) { int r = obj->trunnel_error_code_; obj->trunnel_error_code_ = 0; return r; } ssize_t -signed_tree_head_v1_encode(uint8_t *output, const size_t avail, const signed_tree_head_v1_t *obj) +sigident_ed25519_encode(uint8_t *output, const size_t avail, const sigident_ed25519_t *obj) { ssize_t result = 0; size_t written = 0; uint8_t *ptr = output; const char *msg; #ifdef TRUNNEL_CHECK_ENCODED_LEN - const ssize_t encoded_len = signed_tree_head_v1_encoded_len(obj); + const ssize_t encoded_len = sigident_ed25519_encoded_len(obj); #endif - if (NULL != (msg = signed_tree_head_v1_check(obj))) + if (NULL != (msg = sigident_ed25519_check(obj))) goto check_failed; #ifdef TRUNNEL_CHECK_ENCODED_LEN trunnel_assert(encoded_len >= 0); #endif - /* Encode u64 format IN [T_SIGNED_TREE_HEAD_V1] */ - trunnel_assert(written <= avail); - if (avail - written < 8) - goto truncated; - trunnel_set_uint64(ptr, trunnel_htonll(obj->format)); - written += 8; ptr += 8; - - /* Encode u64 timestamp */ - trunnel_assert(written <= avail); - if (avail - written < 8) - goto truncated; - trunnel_set_uint64(ptr, trunnel_htonll(obj->timestamp)); - written += 8; ptr += 8; - - /* Encode u64 tree_size */ + /* Encode u8 signature[64] */ trunnel_assert(written <= avail); - if (avail - written < 8) + if (avail - written < 64) goto truncated; - trunnel_set_uint64(ptr, trunnel_htonll(obj->tree_size)); - written += 8; ptr += 8; + memcpy(ptr, obj->signature, 64); + written += 64; ptr += 64; - /* Encode u8 root_hash[32] */ + /* Encode u8 identifier[32] */ trunnel_assert(written <= avail); if (avail - written < 32) goto truncated; - memcpy(ptr, obj->root_hash, 32); + memcpy(ptr, obj->identifier, 32); written += 32; ptr += 32; - /* Encode u64 length */ - trunnel_assert(written <= avail); - if (avail - written < 8) - goto truncated; - trunnel_set_uint64(ptr, trunnel_htonll(obj->length)); - written += 8; ptr += 8; - - /* Encode u8 sigident[length] */ - { - size_t elt_len = TRUNNEL_DYNARRAY_LEN(&obj->sigident); - trunnel_assert(obj->length == elt_len); - trunnel_assert(written <= avail); - if (avail - written < elt_len) - goto truncated; - if (elt_len) - memcpy(ptr, obj->sigident.elts_, elt_len); - written += elt_len; ptr += elt_len; - } - trunnel_assert(ptr == output + written); #ifdef TRUNNEL_CHECK_ENCODED_LEN @@ -1124,364 +955,1191 @@ signed_tree_head_v1_encode(uint8_t *output, const size_t avail, const signed_tre return result; } -/** As signed_tree_head_v1_parse(), but do not allocate the output +/** As sigident_ed25519_parse(), but do not allocate the output * object. */ static ssize_t -signed_tree_head_v1_parse_into(signed_tree_head_v1_t *obj, const uint8_t *input, const size_t len_in) +sigident_ed25519_parse_into(sigident_ed25519_t *obj, const uint8_t *input, const size_t len_in) { const uint8_t *ptr = input; size_t remaining = len_in; ssize_t result = 0; (void)result; - /* Parse u64 format IN [T_SIGNED_TREE_HEAD_V1] */ - CHECK_REMAINING(8, truncated); - obj->format = trunnel_ntohll(trunnel_get_uint64(ptr)); - remaining -= 8; ptr += 8; - if (! (obj->format == T_SIGNED_TREE_HEAD_V1)) - goto fail; - - /* Parse u64 timestamp */ - CHECK_REMAINING(8, truncated); - obj->timestamp = trunnel_ntohll(trunnel_get_uint64(ptr)); - remaining -= 8; ptr += 8; - - /* Parse u64 tree_size */ - CHECK_REMAINING(8, truncated); - obj->tree_size = trunnel_ntohll(trunnel_get_uint64(ptr)); - remaining -= 8; ptr += 8; + /* Parse u8 signature[64] */ + CHECK_REMAINING(64, truncated); + memcpy(obj->signature, ptr, 64); + remaining -= 64; ptr += 64; - /* Parse u8 root_hash[32] */ + /* Parse u8 identifier[32] */ CHECK_REMAINING(32, truncated); - memcpy(obj->root_hash, ptr, 32); + memcpy(obj->identifier, ptr, 32); remaining -= 32; ptr += 32; - - /* Parse u64 length */ - CHECK_REMAINING(8, truncated); - obj->length = trunnel_ntohll(trunnel_get_uint64(ptr)); - remaining -= 8; ptr += 8; - - /* Parse u8 sigident[length] */ - CHECK_REMAINING(obj->length, truncated); - TRUNNEL_DYNARRAY_EXPAND(uint8_t, &obj->sigident, obj->length, {}); - obj->sigident.n_ = obj->length; - if (obj->length) - memcpy(obj->sigident.elts_, ptr, obj->length); - ptr += obj->length; remaining -= obj->length; trunnel_assert(ptr + remaining == input + len_in); return len_in - remaining; truncated: return -2; - trunnel_alloc_failed: - return -1; - fail: - result = -1; - return result; } ssize_t -signed_tree_head_v1_parse(signed_tree_head_v1_t **output, const uint8_t *input, const size_t len_in) +sigident_ed25519_parse(sigident_ed25519_t **output, const uint8_t *input, const size_t len_in) { ssize_t result; - *output = signed_tree_head_v1_new(); + *output = sigident_ed25519_new(); if (NULL == *output) return -1; - result = signed_tree_head_v1_parse_into(*output, input, len_in); + result = sigident_ed25519_parse_into(*output, input, len_in); if (result < 0) { - signed_tree_head_v1_free(*output); + sigident_ed25519_free(*output); *output = NULL; } return result; } -consistency_proof_v1_t * -consistency_proof_v1_new(void) +signed_checksum32_ed25519_t * +signed_checksum32_ed25519_new(void) { - consistency_proof_v1_t *val = trunnel_calloc(1, sizeof(consistency_proof_v1_t)); + signed_checksum32_ed25519_t *val = trunnel_calloc(1, sizeof(signed_checksum32_ed25519_t)); if (NULL == val) return NULL; - val->format = T_CONSISTENCY_PROOF_V1; + val->length = 1; return val; } /** Release all storage held inside 'obj', but do not free 'obj'. */ static void -consistency_proof_v1_clear(consistency_proof_v1_t *obj) +signed_checksum32_ed25519_clear(signed_checksum32_ed25519_t *obj) { (void) obj; - ed25519_v1_free(obj->identifier); - obj->identifier = NULL; - TRUNNEL_DYNARRAY_WIPE(&obj->hashes); - TRUNNEL_DYNARRAY_CLEAR(&obj->hashes); + TRUNNEL_DYNARRAY_WIPE(&obj->identifier); + TRUNNEL_DYNARRAY_CLEAR(&obj->identifier); } void -consistency_proof_v1_free(consistency_proof_v1_t *obj) +signed_checksum32_ed25519_free(signed_checksum32_ed25519_t *obj) { if (obj == NULL) return; - consistency_proof_v1_clear(obj); - trunnel_memwipe(obj, sizeof(consistency_proof_v1_t)); + signed_checksum32_ed25519_clear(obj); + trunnel_memwipe(obj, sizeof(signed_checksum32_ed25519_t)); trunnel_free_(obj); } -uint64_t -consistency_proof_v1_get_format(const consistency_proof_v1_t *inp) +size_t +signed_checksum32_ed25519_getlen_checksum(const signed_checksum32_ed25519_t *inp) { - return inp->format; + (void)inp; return 32; } -int -consistency_proof_v1_set_format(consistency_proof_v1_t *inp, uint64_t val) -{ - if (! ((val == T_CONSISTENCY_PROOF_V1))) { + +uint8_t +signed_checksum32_ed25519_get_checksum(signed_checksum32_ed25519_t *inp, size_t idx) +{ + trunnel_assert(idx < 32); + return inp->checksum[idx]; +} + +uint8_t +signed_checksum32_ed25519_getconst_checksum(const signed_checksum32_ed25519_t *inp, size_t idx) +{ + return signed_checksum32_ed25519_get_checksum((signed_checksum32_ed25519_t*)inp, idx); +} +int +signed_checksum32_ed25519_set_checksum(signed_checksum32_ed25519_t *inp, size_t idx, uint8_t elt) +{ + trunnel_assert(idx < 32); + inp->checksum[idx] = elt; + return 0; +} + +uint8_t * +signed_checksum32_ed25519_getarray_checksum(signed_checksum32_ed25519_t *inp) +{ + return inp->checksum; +} +const uint8_t * +signed_checksum32_ed25519_getconstarray_checksum(const signed_checksum32_ed25519_t *inp) +{ + return (const uint8_t *)signed_checksum32_ed25519_getarray_checksum((signed_checksum32_ed25519_t*)inp); +} +uint64_t +signed_checksum32_ed25519_get_length(const signed_checksum32_ed25519_t *inp) +{ + return inp->length; +} +int +signed_checksum32_ed25519_set_length(signed_checksum32_ed25519_t *inp, uint64_t val) +{ + if (! (((val >= 1 && val <= 128)))) { TRUNNEL_SET_ERROR_CODE(inp); return -1; } - inp->format = val; + inp->length = val; + return 0; +} +size_t +signed_checksum32_ed25519_getlen_identifier(const signed_checksum32_ed25519_t *inp) +{ + return TRUNNEL_DYNARRAY_LEN(&inp->identifier); +} + +uint8_t +signed_checksum32_ed25519_get_identifier(signed_checksum32_ed25519_t *inp, size_t idx) +{ + return TRUNNEL_DYNARRAY_GET(&inp->identifier, idx); +} + +uint8_t +signed_checksum32_ed25519_getconst_identifier(const signed_checksum32_ed25519_t *inp, size_t idx) +{ + return signed_checksum32_ed25519_get_identifier((signed_checksum32_ed25519_t*)inp, idx); +} +int +signed_checksum32_ed25519_set_identifier(signed_checksum32_ed25519_t *inp, size_t idx, uint8_t elt) +{ + TRUNNEL_DYNARRAY_SET(&inp->identifier, idx, elt); + return 0; +} +int +signed_checksum32_ed25519_add_identifier(signed_checksum32_ed25519_t *inp, uint8_t elt) +{ +#if SIZE_MAX >= UINT64_MAX + if (inp->identifier.n_ == UINT64_MAX) + goto trunnel_alloc_failed; +#endif + TRUNNEL_DYNARRAY_ADD(uint8_t, &inp->identifier, elt, {}); + return 0; + trunnel_alloc_failed: + TRUNNEL_SET_ERROR_CODE(inp); + return -1; +} + +uint8_t * +signed_checksum32_ed25519_getarray_identifier(signed_checksum32_ed25519_t *inp) +{ + return inp->identifier.elts_; +} +const uint8_t * +signed_checksum32_ed25519_getconstarray_identifier(const signed_checksum32_ed25519_t *inp) +{ + return (const uint8_t *)signed_checksum32_ed25519_getarray_identifier((signed_checksum32_ed25519_t*)inp); +} +int +signed_checksum32_ed25519_setlen_identifier(signed_checksum32_ed25519_t *inp, size_t newlen) +{ + uint8_t *newptr; +#if UINT64_MAX < SIZE_MAX + if (newlen > UINT64_MAX) + goto trunnel_alloc_failed; +#endif + newptr = trunnel_dynarray_setlen(&inp->identifier.allocated_, + &inp->identifier.n_, inp->identifier.elts_, newlen, + sizeof(inp->identifier.elts_[0]), (trunnel_free_fn_t) NULL, + &inp->trunnel_error_code_); + if (newlen != 0 && newptr == NULL) + goto trunnel_alloc_failed; + inp->identifier.elts_ = newptr; + return 0; + trunnel_alloc_failed: + TRUNNEL_SET_ERROR_CODE(inp); + return -1; +} +size_t +signed_checksum32_ed25519_getlen_signature(const signed_checksum32_ed25519_t *inp) +{ + (void)inp; return 64; +} + +uint8_t +signed_checksum32_ed25519_get_signature(signed_checksum32_ed25519_t *inp, size_t idx) +{ + trunnel_assert(idx < 64); + return inp->signature[idx]; +} + +uint8_t +signed_checksum32_ed25519_getconst_signature(const signed_checksum32_ed25519_t *inp, size_t idx) +{ + return signed_checksum32_ed25519_get_signature((signed_checksum32_ed25519_t*)inp, idx); +} +int +signed_checksum32_ed25519_set_signature(signed_checksum32_ed25519_t *inp, size_t idx, uint8_t elt) +{ + trunnel_assert(idx < 64); + inp->signature[idx] = elt; + return 0; +} + +uint8_t * +signed_checksum32_ed25519_getarray_signature(signed_checksum32_ed25519_t *inp) +{ + return inp->signature; +} +const uint8_t * +signed_checksum32_ed25519_getconstarray_signature(const signed_checksum32_ed25519_t *inp) +{ + return (const uint8_t *)signed_checksum32_ed25519_getarray_signature((signed_checksum32_ed25519_t*)inp); +} +size_t +signed_checksum32_ed25519_getlen_namespace(const signed_checksum32_ed25519_t *inp) +{ + (void)inp; return 32; +} + +uint8_t +signed_checksum32_ed25519_get_namespace(signed_checksum32_ed25519_t *inp, size_t idx) +{ + trunnel_assert(idx < 32); + return inp->namespace[idx]; +} + +uint8_t +signed_checksum32_ed25519_getconst_namespace(const signed_checksum32_ed25519_t *inp, size_t idx) +{ + return signed_checksum32_ed25519_get_namespace((signed_checksum32_ed25519_t*)inp, idx); +} +int +signed_checksum32_ed25519_set_namespace(signed_checksum32_ed25519_t *inp, size_t idx, uint8_t elt) +{ + trunnel_assert(idx < 32); + inp->namespace[idx] = elt; return 0; } -struct ed25519_v1_st * -consistency_proof_v1_get_identifier(consistency_proof_v1_t *inp) + +uint8_t * +signed_checksum32_ed25519_getarray_namespace(signed_checksum32_ed25519_t *inp) +{ + return inp->namespace; +} +const uint8_t * +signed_checksum32_ed25519_getconstarray_namespace(const signed_checksum32_ed25519_t *inp) +{ + return (const uint8_t *)signed_checksum32_ed25519_getarray_namespace((signed_checksum32_ed25519_t*)inp); +} +const char * +signed_checksum32_ed25519_check(const signed_checksum32_ed25519_t *obj) +{ + if (obj == NULL) + return "Object was NULL"; + if (obj->trunnel_error_code_) + return "A set function failed on this object"; + if (! ((obj->length >= 1 && obj->length <= 128))) + return "Integer out of bounds"; + if (TRUNNEL_DYNARRAY_LEN(&obj->identifier) != obj->length) + return "Length mismatch for identifier"; + return NULL; +} + +ssize_t +signed_checksum32_ed25519_encoded_len(const signed_checksum32_ed25519_t *obj) +{ + ssize_t result = 0; + + if (NULL != signed_checksum32_ed25519_check(obj)) + return -1; + + + /* Length of u8 checksum[32] */ + result += 32; + + /* Length of u64 length IN [1..128] */ + result += 8; + + /* Length of u8 identifier[length] */ + result += TRUNNEL_DYNARRAY_LEN(&obj->identifier); + + /* Length of u8 signature[64] */ + result += 64; + + /* Length of u8 namespace[32] */ + result += 32; + return result; +} +int +signed_checksum32_ed25519_clear_errors(signed_checksum32_ed25519_t *obj) +{ + int r = obj->trunnel_error_code_; + obj->trunnel_error_code_ = 0; + return r; +} +ssize_t +signed_checksum32_ed25519_encode(uint8_t *output, const size_t avail, const signed_checksum32_ed25519_t *obj) +{ + ssize_t result = 0; + size_t written = 0; + uint8_t *ptr = output; + const char *msg; +#ifdef TRUNNEL_CHECK_ENCODED_LEN + const ssize_t encoded_len = signed_checksum32_ed25519_encoded_len(obj); +#endif + + if (NULL != (msg = signed_checksum32_ed25519_check(obj))) + goto check_failed; + +#ifdef TRUNNEL_CHECK_ENCODED_LEN + trunnel_assert(encoded_len >= 0); +#endif + + /* Encode u8 checksum[32] */ + trunnel_assert(written <= avail); + if (avail - written < 32) + goto truncated; + memcpy(ptr, obj->checksum, 32); + written += 32; ptr += 32; + + /* Encode u64 length IN [1..128] */ + trunnel_assert(written <= avail); + if (avail - written < 8) + goto truncated; + trunnel_set_uint64(ptr, trunnel_htonll(obj->length)); + written += 8; ptr += 8; + + /* Encode u8 identifier[length] */ + { + size_t elt_len = TRUNNEL_DYNARRAY_LEN(&obj->identifier); + trunnel_assert(obj->length == elt_len); + trunnel_assert(written <= avail); + if (avail - written < elt_len) + goto truncated; + if (elt_len) + memcpy(ptr, obj->identifier.elts_, elt_len); + written += elt_len; ptr += elt_len; + } + + /* Encode u8 signature[64] */ + trunnel_assert(written <= avail); + if (avail - written < 64) + goto truncated; + memcpy(ptr, obj->signature, 64); + written += 64; ptr += 64; + + /* Encode u8 namespace[32] */ + trunnel_assert(written <= avail); + if (avail - written < 32) + goto truncated; + memcpy(ptr, obj->namespace, 32); + written += 32; ptr += 32; + + + trunnel_assert(ptr == output + written); +#ifdef TRUNNEL_CHECK_ENCODED_LEN + { + trunnel_assert(encoded_len >= 0); + trunnel_assert((size_t)encoded_len == written); + } + +#endif + + return written; + + truncated: + result = -2; + goto fail; + check_failed: + (void)msg; + result = -1; + goto fail; + fail: + trunnel_assert(result < 0); + return result; +} + +/** As signed_checksum32_ed25519_parse(), but do not allocate the + * output object. + */ +static ssize_t +signed_checksum32_ed25519_parse_into(signed_checksum32_ed25519_t *obj, const uint8_t *input, const size_t len_in) +{ + const uint8_t *ptr = input; + size_t remaining = len_in; + ssize_t result = 0; + (void)result; + + /* Parse u8 checksum[32] */ + CHECK_REMAINING(32, truncated); + memcpy(obj->checksum, ptr, 32); + remaining -= 32; ptr += 32; + + /* Parse u64 length IN [1..128] */ + CHECK_REMAINING(8, truncated); + obj->length = trunnel_ntohll(trunnel_get_uint64(ptr)); + remaining -= 8; ptr += 8; + if (! ((obj->length >= 1 && obj->length <= 128))) + goto fail; + + /* Parse u8 identifier[length] */ + CHECK_REMAINING(obj->length, truncated); + TRUNNEL_DYNARRAY_EXPAND(uint8_t, &obj->identifier, obj->length, {}); + obj->identifier.n_ = obj->length; + if (obj->length) + memcpy(obj->identifier.elts_, ptr, obj->length); + ptr += obj->length; remaining -= obj->length; + + /* Parse u8 signature[64] */ + CHECK_REMAINING(64, truncated); + memcpy(obj->signature, ptr, 64); + remaining -= 64; ptr += 64; + + /* Parse u8 namespace[32] */ + CHECK_REMAINING(32, truncated); + memcpy(obj->namespace, ptr, 32); + remaining -= 32; ptr += 32; + trunnel_assert(ptr + remaining == input + len_in); + return len_in - remaining; + + truncated: + return -2; + trunnel_alloc_failed: + return -1; + fail: + result = -1; + return result; +} + +ssize_t +signed_checksum32_ed25519_parse(signed_checksum32_ed25519_t **output, const uint8_t *input, const size_t len_in) +{ + ssize_t result; + *output = signed_checksum32_ed25519_new(); + if (NULL == *output) + return -1; + result = signed_checksum32_ed25519_parse_into(*output, input, len_in); + if (result < 0) { + signed_checksum32_ed25519_free(*output); + *output = NULL; + } + return result; +} +consistency_proof_v1_t * +consistency_proof_v1_new(void) +{ + consistency_proof_v1_t *val = trunnel_calloc(1, sizeof(consistency_proof_v1_t)); + if (NULL == val) + return NULL; + val->magic = MAGIC_V1; + val->format = T_CONSISTENCY_PROOF_V1; + return val; +} + +/** Release all storage held inside 'obj', but do not free 'obj'. + */ +static void +consistency_proof_v1_clear(consistency_proof_v1_t *obj) +{ + (void) obj; + { + + unsigned idx; + for (idx = 0; idx < TRUNNEL_DYNARRAY_LEN(&obj->hashes); ++idx) { + hash_free(TRUNNEL_DYNARRAY_GET(&obj->hashes, idx)); + } + } + TRUNNEL_DYNARRAY_WIPE(&obj->hashes); + TRUNNEL_DYNARRAY_CLEAR(&obj->hashes); +} + +void +consistency_proof_v1_free(consistency_proof_v1_t *obj) +{ + if (obj == NULL) + return; + consistency_proof_v1_clear(obj); + trunnel_memwipe(obj, sizeof(consistency_proof_v1_t)); + trunnel_free_(obj); +} + +uint64_t +consistency_proof_v1_get_magic(const consistency_proof_v1_t *inp) +{ + return inp->magic; +} +int +consistency_proof_v1_set_magic(consistency_proof_v1_t *inp, uint64_t val) +{ + if (! ((val == MAGIC_V1))) { + TRUNNEL_SET_ERROR_CODE(inp); + return -1; + } + inp->magic = val; + return 0; +} +uint64_t +consistency_proof_v1_get_format(const consistency_proof_v1_t *inp) +{ + return inp->format; +} +int +consistency_proof_v1_set_format(consistency_proof_v1_t *inp, uint64_t val) +{ + if (! ((val == T_CONSISTENCY_PROOF_V1))) { + TRUNNEL_SET_ERROR_CODE(inp); + return -1; + } + inp->format = val; + return 0; +} +size_t +consistency_proof_v1_getlen_identifier(const consistency_proof_v1_t *inp) +{ + (void)inp; return 32; +} + +uint8_t +consistency_proof_v1_get_identifier(consistency_proof_v1_t *inp, size_t idx) +{ + trunnel_assert(idx < 32); + return inp->identifier[idx]; +} + +uint8_t +consistency_proof_v1_getconst_identifier(const consistency_proof_v1_t *inp, size_t idx) +{ + return consistency_proof_v1_get_identifier((consistency_proof_v1_t*)inp, idx); +} +int +consistency_proof_v1_set_identifier(consistency_proof_v1_t *inp, size_t idx, uint8_t elt) +{ + trunnel_assert(idx < 32); + inp->identifier[idx] = elt; + return 0; +} + +uint8_t * +consistency_proof_v1_getarray_identifier(consistency_proof_v1_t *inp) +{ + return inp->identifier; +} +const uint8_t * +consistency_proof_v1_getconstarray_identifier(const consistency_proof_v1_t *inp) +{ + return (const uint8_t *)consistency_proof_v1_getarray_identifier((consistency_proof_v1_t*)inp); +} +uint64_t +consistency_proof_v1_get_old_size(const consistency_proof_v1_t *inp) +{ + return inp->old_size; +} +int +consistency_proof_v1_set_old_size(consistency_proof_v1_t *inp, uint64_t val) +{ + inp->old_size = val; + return 0; +} +uint64_t +consistency_proof_v1_get_new_size(const consistency_proof_v1_t *inp) +{ + return inp->new_size; +} +int +consistency_proof_v1_set_new_size(consistency_proof_v1_t *inp, uint64_t val) +{ + inp->new_size = val; + return 0; +} +uint64_t +consistency_proof_v1_get_n_items(const consistency_proof_v1_t *inp) +{ + return inp->n_items; +} +int +consistency_proof_v1_set_n_items(consistency_proof_v1_t *inp, uint64_t val) +{ + inp->n_items = val; + return 0; +} +size_t +consistency_proof_v1_getlen_hashes(const consistency_proof_v1_t *inp) +{ + return TRUNNEL_DYNARRAY_LEN(&inp->hashes); +} + +struct hash_st * +consistency_proof_v1_get_hashes(consistency_proof_v1_t *inp, size_t idx) +{ + return TRUNNEL_DYNARRAY_GET(&inp->hashes, idx); +} + + const struct hash_st * +consistency_proof_v1_getconst_hashes(const consistency_proof_v1_t *inp, size_t idx) +{ + return consistency_proof_v1_get_hashes((consistency_proof_v1_t*)inp, idx); +} +int +consistency_proof_v1_set_hashes(consistency_proof_v1_t *inp, size_t idx, struct hash_st * elt) +{ + hash_t *oldval = TRUNNEL_DYNARRAY_GET(&inp->hashes, idx); + if (oldval && oldval != elt) + hash_free(oldval); + return consistency_proof_v1_set0_hashes(inp, idx, elt); +} +int +consistency_proof_v1_set0_hashes(consistency_proof_v1_t *inp, size_t idx, struct hash_st * elt) +{ + TRUNNEL_DYNARRAY_SET(&inp->hashes, idx, elt); + return 0; +} +int +consistency_proof_v1_add_hashes(consistency_proof_v1_t *inp, struct hash_st * elt) +{ +#if SIZE_MAX >= UINT64_MAX + if (inp->hashes.n_ == UINT64_MAX) + goto trunnel_alloc_failed; +#endif + TRUNNEL_DYNARRAY_ADD(struct hash_st *, &inp->hashes, elt, {}); + return 0; + trunnel_alloc_failed: + TRUNNEL_SET_ERROR_CODE(inp); + return -1; +} + +struct hash_st * * +consistency_proof_v1_getarray_hashes(consistency_proof_v1_t *inp) +{ + return inp->hashes.elts_; +} +const struct hash_st * const * +consistency_proof_v1_getconstarray_hashes(const consistency_proof_v1_t *inp) +{ + return (const struct hash_st * const *)consistency_proof_v1_getarray_hashes((consistency_proof_v1_t*)inp); +} +int +consistency_proof_v1_setlen_hashes(consistency_proof_v1_t *inp, size_t newlen) +{ + struct hash_st * *newptr; +#if UINT64_MAX < SIZE_MAX + if (newlen > UINT64_MAX) + goto trunnel_alloc_failed; +#endif + newptr = trunnel_dynarray_setlen(&inp->hashes.allocated_, + &inp->hashes.n_, inp->hashes.elts_, newlen, + sizeof(inp->hashes.elts_[0]), (trunnel_free_fn_t) hash_free, + &inp->trunnel_error_code_); + if (newlen != 0 && newptr == NULL) + goto trunnel_alloc_failed; + inp->hashes.elts_ = newptr; + return 0; + trunnel_alloc_failed: + TRUNNEL_SET_ERROR_CODE(inp); + return -1; +} +const char * +consistency_proof_v1_check(const consistency_proof_v1_t *obj) +{ + if (obj == NULL) + return "Object was NULL"; + if (obj->trunnel_error_code_) + return "A set function failed on this object"; + if (! (obj->magic == MAGIC_V1)) + return "Integer out of bounds"; + if (! (obj->format == T_CONSISTENCY_PROOF_V1)) + return "Integer out of bounds"; + { + const char *msg; + + unsigned idx; + for (idx = 0; idx < TRUNNEL_DYNARRAY_LEN(&obj->hashes); ++idx) { + if (NULL != (msg = hash_check(TRUNNEL_DYNARRAY_GET(&obj->hashes, idx)))) + return msg; + } + } + if (TRUNNEL_DYNARRAY_LEN(&obj->hashes) != obj->n_items) + return "Length mismatch for hashes"; + return NULL; +} + +ssize_t +consistency_proof_v1_encoded_len(const consistency_proof_v1_t *obj) +{ + ssize_t result = 0; + + if (NULL != consistency_proof_v1_check(obj)) + return -1; + + + /* Length of u64 magic IN [MAGIC_V1] */ + result += 8; + + /* Length of u64 format IN [T_CONSISTENCY_PROOF_V1] */ + result += 8; + + /* Length of u8 identifier[32] */ + result += 32; + + /* Length of u64 old_size */ + result += 8; + + /* Length of u64 new_size */ + result += 8; + + /* Length of u64 n_items */ + result += 8; + + /* Length of struct hash hashes[n_items] */ + { + + unsigned idx; + for (idx = 0; idx < TRUNNEL_DYNARRAY_LEN(&obj->hashes); ++idx) { + result += hash_encoded_len(TRUNNEL_DYNARRAY_GET(&obj->hashes, idx)); + } + } + return result; +} +int +consistency_proof_v1_clear_errors(consistency_proof_v1_t *obj) +{ + int r = obj->trunnel_error_code_; + obj->trunnel_error_code_ = 0; + return r; +} +ssize_t +consistency_proof_v1_encode(uint8_t *output, const size_t avail, const consistency_proof_v1_t *obj) +{ + ssize_t result = 0; + size_t written = 0; + uint8_t *ptr = output; + const char *msg; +#ifdef TRUNNEL_CHECK_ENCODED_LEN + const ssize_t encoded_len = consistency_proof_v1_encoded_len(obj); +#endif + + if (NULL != (msg = consistency_proof_v1_check(obj))) + goto check_failed; + +#ifdef TRUNNEL_CHECK_ENCODED_LEN + trunnel_assert(encoded_len >= 0); +#endif + + /* Encode u64 magic IN [MAGIC_V1] */ + trunnel_assert(written <= avail); + if (avail - written < 8) + goto truncated; + trunnel_set_uint64(ptr, trunnel_htonll(obj->magic)); + written += 8; ptr += 8; + + /* Encode u64 format IN [T_CONSISTENCY_PROOF_V1] */ + trunnel_assert(written <= avail); + if (avail - written < 8) + goto truncated; + trunnel_set_uint64(ptr, trunnel_htonll(obj->format)); + written += 8; ptr += 8; + + /* Encode u8 identifier[32] */ + trunnel_assert(written <= avail); + if (avail - written < 32) + goto truncated; + memcpy(ptr, obj->identifier, 32); + written += 32; ptr += 32; + + /* Encode u64 old_size */ + trunnel_assert(written <= avail); + if (avail - written < 8) + goto truncated; + trunnel_set_uint64(ptr, trunnel_htonll(obj->old_size)); + written += 8; ptr += 8; + + /* Encode u64 new_size */ + trunnel_assert(written <= avail); + if (avail - written < 8) + goto truncated; + trunnel_set_uint64(ptr, trunnel_htonll(obj->new_size)); + written += 8; ptr += 8; + + /* Encode u64 n_items */ + trunnel_assert(written <= avail); + if (avail - written < 8) + goto truncated; + trunnel_set_uint64(ptr, trunnel_htonll(obj->n_items)); + written += 8; ptr += 8; + + /* Encode struct hash hashes[n_items] */ + { + + unsigned idx; + for (idx = 0; idx < TRUNNEL_DYNARRAY_LEN(&obj->hashes); ++idx) { + trunnel_assert(written <= avail); + result = hash_encode(ptr, avail - written, TRUNNEL_DYNARRAY_GET(&obj->hashes, idx)); + if (result < 0) + goto fail; /* XXXXXXX !*/ + written += result; ptr += result; + } + } + + + trunnel_assert(ptr == output + written); +#ifdef TRUNNEL_CHECK_ENCODED_LEN + { + trunnel_assert(encoded_len >= 0); + trunnel_assert((size_t)encoded_len == written); + } + +#endif + + return written; + + truncated: + result = -2; + goto fail; + check_failed: + (void)msg; + result = -1; + goto fail; + fail: + trunnel_assert(result < 0); + return result; +} + +/** As consistency_proof_v1_parse(), but do not allocate the output + * object. + */ +static ssize_t +consistency_proof_v1_parse_into(consistency_proof_v1_t *obj, const uint8_t *input, const size_t len_in) +{ + const uint8_t *ptr = input; + size_t remaining = len_in; + ssize_t result = 0; + (void)result; + + /* Parse u64 magic IN [MAGIC_V1] */ + CHECK_REMAINING(8, truncated); + obj->magic = trunnel_ntohll(trunnel_get_uint64(ptr)); + remaining -= 8; ptr += 8; + if (! (obj->magic == MAGIC_V1)) + goto fail; + + /* Parse u64 format IN [T_CONSISTENCY_PROOF_V1] */ + CHECK_REMAINING(8, truncated); + obj->format = trunnel_ntohll(trunnel_get_uint64(ptr)); + remaining -= 8; ptr += 8; + if (! (obj->format == T_CONSISTENCY_PROOF_V1)) + goto fail; + + /* Parse u8 identifier[32] */ + CHECK_REMAINING(32, truncated); + memcpy(obj->identifier, ptr, 32); + remaining -= 32; ptr += 32; + + /* Parse u64 old_size */ + CHECK_REMAINING(8, truncated); + obj->old_size = trunnel_ntohll(trunnel_get_uint64(ptr)); + remaining -= 8; ptr += 8; + + /* Parse u64 new_size */ + CHECK_REMAINING(8, truncated); + obj->new_size = trunnel_ntohll(trunnel_get_uint64(ptr)); + remaining -= 8; ptr += 8; + + /* Parse u64 n_items */ + CHECK_REMAINING(8, truncated); + obj->n_items = trunnel_ntohll(trunnel_get_uint64(ptr)); + remaining -= 8; ptr += 8; + + /* Parse struct hash hashes[n_items] */ + TRUNNEL_DYNARRAY_EXPAND(hash_t *, &obj->hashes, obj->n_items, {}); + { + hash_t * elt; + unsigned idx; + for (idx = 0; idx < obj->n_items; ++idx) { + result = hash_parse(&elt, ptr, remaining); + if (result < 0) + goto relay_fail; + trunnel_assert((size_t)result <= remaining); + remaining -= result; ptr += result; + TRUNNEL_DYNARRAY_ADD(hash_t *, &obj->hashes, elt, {hash_free(elt);}); + } + } + trunnel_assert(ptr + remaining == input + len_in); + return len_in - remaining; + + truncated: + return -2; + relay_fail: + trunnel_assert(result < 0); + return result; + trunnel_alloc_failed: + return -1; + fail: + result = -1; + return result; +} + +ssize_t +consistency_proof_v1_parse(consistency_proof_v1_t **output, const uint8_t *input, const size_t len_in) { - return inp->identifier; + ssize_t result; + *output = consistency_proof_v1_new(); + if (NULL == *output) + return -1; + result = consistency_proof_v1_parse_into(*output, input, len_in); + if (result < 0) { + consistency_proof_v1_free(*output); + *output = NULL; + } + return result; } -const struct ed25519_v1_st * -consistency_proof_v1_getconst_identifier(const consistency_proof_v1_t *inp) +entries_v1_t * +entries_v1_new(void) { - return consistency_proof_v1_get_identifier((consistency_proof_v1_t*) inp); + entries_v1_t *val = trunnel_calloc(1, sizeof(entries_v1_t)); + if (NULL == val) + return NULL; + val->magic = MAGIC_V1; + val->format = T_ENTRIES_V1; + return val; } -int -consistency_proof_v1_set_identifier(consistency_proof_v1_t *inp, struct ed25519_v1_st *val) + +/** Release all storage held inside 'obj', but do not free 'obj'. + */ +static void +entries_v1_clear(entries_v1_t *obj) { - if (inp->identifier && inp->identifier != val) - ed25519_v1_free(inp->identifier); - return consistency_proof_v1_set0_identifier(inp, val); + (void) obj; + { + + unsigned idx; + for (idx = 0; idx < TRUNNEL_DYNARRAY_LEN(&obj->checksums); ++idx) { + signed_checksum32_ed25519_free(TRUNNEL_DYNARRAY_GET(&obj->checksums, idx)); + } + } + TRUNNEL_DYNARRAY_WIPE(&obj->checksums); + TRUNNEL_DYNARRAY_CLEAR(&obj->checksums); } -int -consistency_proof_v1_set0_identifier(consistency_proof_v1_t *inp, struct ed25519_v1_st *val) + +void +entries_v1_free(entries_v1_t *obj) { - inp->identifier = val; - return 0; + if (obj == NULL) + return; + entries_v1_clear(obj); + trunnel_memwipe(obj, sizeof(entries_v1_t)); + trunnel_free_(obj); } + uint64_t -consistency_proof_v1_get_old_size(const consistency_proof_v1_t *inp) +entries_v1_get_magic(const entries_v1_t *inp) { - return inp->old_size; + return inp->magic; } int -consistency_proof_v1_set_old_size(consistency_proof_v1_t *inp, uint64_t val) +entries_v1_set_magic(entries_v1_t *inp, uint64_t val) { - inp->old_size = val; + if (! ((val == MAGIC_V1))) { + TRUNNEL_SET_ERROR_CODE(inp); + return -1; + } + inp->magic = val; return 0; } uint64_t -consistency_proof_v1_get_new_size(const consistency_proof_v1_t *inp) +entries_v1_get_format(const entries_v1_t *inp) { - return inp->new_size; + return inp->format; } int -consistency_proof_v1_set_new_size(consistency_proof_v1_t *inp, uint64_t val) +entries_v1_set_format(entries_v1_t *inp, uint64_t val) { - inp->new_size = val; + if (! ((val == T_ENTRIES_V1))) { + TRUNNEL_SET_ERROR_CODE(inp); + return -1; + } + inp->format = val; return 0; } uint64_t -consistency_proof_v1_get_length(const consistency_proof_v1_t *inp) +entries_v1_get_n_items(const entries_v1_t *inp) { - return inp->length; + return inp->n_items; } int -consistency_proof_v1_set_length(consistency_proof_v1_t *inp, uint64_t val) +entries_v1_set_n_items(entries_v1_t *inp, uint64_t val) { - inp->length = val; + inp->n_items = val; return 0; } size_t -consistency_proof_v1_getlen_hashes(const consistency_proof_v1_t *inp) +entries_v1_getlen_checksums(const entries_v1_t *inp) { - return TRUNNEL_DYNARRAY_LEN(&inp->hashes); + return TRUNNEL_DYNARRAY_LEN(&inp->checksums); } -uint8_t -consistency_proof_v1_get_hashes(consistency_proof_v1_t *inp, size_t idx) +struct signed_checksum32_ed25519_st * +entries_v1_get_checksums(entries_v1_t *inp, size_t idx) { - return TRUNNEL_DYNARRAY_GET(&inp->hashes, idx); + return TRUNNEL_DYNARRAY_GET(&inp->checksums, idx); } -uint8_t -consistency_proof_v1_getconst_hashes(const consistency_proof_v1_t *inp, size_t idx) + const struct signed_checksum32_ed25519_st * +entries_v1_getconst_checksums(const entries_v1_t *inp, size_t idx) { - return consistency_proof_v1_get_hashes((consistency_proof_v1_t*)inp, idx); + return entries_v1_get_checksums((entries_v1_t*)inp, idx); } int -consistency_proof_v1_set_hashes(consistency_proof_v1_t *inp, size_t idx, uint8_t elt) +entries_v1_set_checksums(entries_v1_t *inp, size_t idx, struct signed_checksum32_ed25519_st * elt) { - TRUNNEL_DYNARRAY_SET(&inp->hashes, idx, elt); + signed_checksum32_ed25519_t *oldval = TRUNNEL_DYNARRAY_GET(&inp->checksums, idx); + if (oldval && oldval != elt) + signed_checksum32_ed25519_free(oldval); + return entries_v1_set0_checksums(inp, idx, elt); +} +int +entries_v1_set0_checksums(entries_v1_t *inp, size_t idx, struct signed_checksum32_ed25519_st * elt) +{ + TRUNNEL_DYNARRAY_SET(&inp->checksums, idx, elt); return 0; } int -consistency_proof_v1_add_hashes(consistency_proof_v1_t *inp, uint8_t elt) +entries_v1_add_checksums(entries_v1_t *inp, struct signed_checksum32_ed25519_st * elt) { #if SIZE_MAX >= UINT64_MAX - if (inp->hashes.n_ == UINT64_MAX) + if (inp->checksums.n_ == UINT64_MAX) goto trunnel_alloc_failed; #endif - TRUNNEL_DYNARRAY_ADD(uint8_t, &inp->hashes, elt, {}); + TRUNNEL_DYNARRAY_ADD(struct signed_checksum32_ed25519_st *, &inp->checksums, elt, {}); return 0; trunnel_alloc_failed: TRUNNEL_SET_ERROR_CODE(inp); return -1; } -uint8_t * -consistency_proof_v1_getarray_hashes(consistency_proof_v1_t *inp) +struct signed_checksum32_ed25519_st * * +entries_v1_getarray_checksums(entries_v1_t *inp) { - return inp->hashes.elts_; + return inp->checksums.elts_; } -const uint8_t * -consistency_proof_v1_getconstarray_hashes(const consistency_proof_v1_t *inp) +const struct signed_checksum32_ed25519_st * const * +entries_v1_getconstarray_checksums(const entries_v1_t *inp) { - return (const uint8_t *)consistency_proof_v1_getarray_hashes((consistency_proof_v1_t*)inp); + return (const struct signed_checksum32_ed25519_st * const *)entries_v1_getarray_checksums((entries_v1_t*)inp); } int -consistency_proof_v1_setlen_hashes(consistency_proof_v1_t *inp, size_t newlen) +entries_v1_setlen_checksums(entries_v1_t *inp, size_t newlen) { - uint8_t *newptr; + struct signed_checksum32_ed25519_st * *newptr; #if UINT64_MAX < SIZE_MAX if (newlen > UINT64_MAX) goto trunnel_alloc_failed; #endif - newptr = trunnel_dynarray_setlen(&inp->hashes.allocated_, - &inp->hashes.n_, inp->hashes.elts_, newlen, - sizeof(inp->hashes.elts_[0]), (trunnel_free_fn_t) NULL, + newptr = trunnel_dynarray_setlen(&inp->checksums.allocated_, + &inp->checksums.n_, inp->checksums.elts_, newlen, + sizeof(inp->checksums.elts_[0]), (trunnel_free_fn_t) signed_checksum32_ed25519_free, &inp->trunnel_error_code_); if (newlen != 0 && newptr == NULL) goto trunnel_alloc_failed; - inp->hashes.elts_ = newptr; + inp->checksums.elts_ = newptr; return 0; trunnel_alloc_failed: TRUNNEL_SET_ERROR_CODE(inp); return -1; } const char * -consistency_proof_v1_check(const consistency_proof_v1_t *obj) +entries_v1_check(const entries_v1_t *obj) { if (obj == NULL) return "Object was NULL"; if (obj->trunnel_error_code_) return "A set function failed on this object"; - if (! (obj->format == T_CONSISTENCY_PROOF_V1)) + if (! (obj->magic == MAGIC_V1)) + return "Integer out of bounds"; + if (! (obj->format == T_ENTRIES_V1)) return "Integer out of bounds"; { const char *msg; - if (NULL != (msg = ed25519_v1_check(obj->identifier))) - return msg; + + unsigned idx; + for (idx = 0; idx < TRUNNEL_DYNARRAY_LEN(&obj->checksums); ++idx) { + if (NULL != (msg = signed_checksum32_ed25519_check(TRUNNEL_DYNARRAY_GET(&obj->checksums, idx)))) + return msg; + } } - if (TRUNNEL_DYNARRAY_LEN(&obj->hashes) != obj->length) - return "Length mismatch for hashes"; + if (TRUNNEL_DYNARRAY_LEN(&obj->checksums) != obj->n_items) + return "Length mismatch for checksums"; return NULL; } ssize_t -consistency_proof_v1_encoded_len(const consistency_proof_v1_t *obj) +entries_v1_encoded_len(const entries_v1_t *obj) { ssize_t result = 0; - if (NULL != consistency_proof_v1_check(obj)) + if (NULL != entries_v1_check(obj)) return -1; - /* Length of u64 format IN [T_CONSISTENCY_PROOF_V1] */ + /* Length of u64 magic IN [MAGIC_V1] */ result += 8; - /* Length of struct ed25519_v1 identifier */ - result += ed25519_v1_encoded_len(obj->identifier); - - /* Length of u64 old_size */ + /* Length of u64 format IN [T_ENTRIES_V1] */ result += 8; - /* Length of u64 new_size */ + /* Length of u64 n_items */ result += 8; - /* Length of u64 length */ - result += 8; + /* Length of struct signed_checksum32_ed25519 checksums[n_items] */ + { - /* Length of u8 hashes[length] */ - result += TRUNNEL_DYNARRAY_LEN(&obj->hashes); + unsigned idx; + for (idx = 0; idx < TRUNNEL_DYNARRAY_LEN(&obj->checksums); ++idx) { + result += signed_checksum32_ed25519_encoded_len(TRUNNEL_DYNARRAY_GET(&obj->checksums, idx)); + } + } return result; } int -consistency_proof_v1_clear_errors(consistency_proof_v1_t *obj) +entries_v1_clear_errors(entries_v1_t *obj) { int r = obj->trunnel_error_code_; obj->trunnel_error_code_ = 0; return r; } ssize_t -consistency_proof_v1_encode(uint8_t *output, const size_t avail, const consistency_proof_v1_t *obj) +entries_v1_encode(uint8_t *output, const size_t avail, const entries_v1_t *obj) { ssize_t result = 0; size_t written = 0; uint8_t *ptr = output; const char *msg; #ifdef TRUNNEL_CHECK_ENCODED_LEN - const ssize_t encoded_len = consistency_proof_v1_encoded_len(obj); + const ssize_t encoded_len = entries_v1_encoded_len(obj); #endif - if (NULL != (msg = consistency_proof_v1_check(obj))) + if (NULL != (msg = entries_v1_check(obj))) goto check_failed; #ifdef TRUNNEL_CHECK_ENCODED_LEN trunnel_assert(encoded_len >= 0); #endif - /* Encode u64 format IN [T_CONSISTENCY_PROOF_V1] */ - trunnel_assert(written <= avail); - if (avail - written < 8) - goto truncated; - trunnel_set_uint64(ptr, trunnel_htonll(obj->format)); - written += 8; ptr += 8; - - /* Encode struct ed25519_v1 identifier */ - trunnel_assert(written <= avail); - result = ed25519_v1_encode(ptr, avail - written, obj->identifier); - if (result < 0) - goto fail; /* XXXXXXX !*/ - written += result; ptr += result; - - /* Encode u64 old_size */ + /* Encode u64 magic IN [MAGIC_V1] */ trunnel_assert(written <= avail); if (avail - written < 8) goto truncated; - trunnel_set_uint64(ptr, trunnel_htonll(obj->old_size)); + trunnel_set_uint64(ptr, trunnel_htonll(obj->magic)); written += 8; ptr += 8; - /* Encode u64 new_size */ + /* Encode u64 format IN [T_ENTRIES_V1] */ trunnel_assert(written <= avail); if (avail - written < 8) goto truncated; - trunnel_set_uint64(ptr, trunnel_htonll(obj->new_size)); + trunnel_set_uint64(ptr, trunnel_htonll(obj->format)); written += 8; ptr += 8; - /* Encode u64 length */ + /* Encode u64 n_items */ trunnel_assert(written <= avail); if (avail - written < 8) goto truncated; - trunnel_set_uint64(ptr, trunnel_htonll(obj->length)); + trunnel_set_uint64(ptr, trunnel_htonll(obj->n_items)); written += 8; ptr += 8; - /* Encode u8 hashes[length] */ + /* Encode struct signed_checksum32_ed25519 checksums[n_items] */ { - size_t elt_len = TRUNNEL_DYNARRAY_LEN(&obj->hashes); - trunnel_assert(obj->length == elt_len); - trunnel_assert(written <= avail); - if (avail - written < elt_len) - goto truncated; - if (elt_len) - memcpy(ptr, obj->hashes.elts_, elt_len); - written += elt_len; ptr += elt_len; + + unsigned idx; + for (idx = 0; idx < TRUNNEL_DYNARRAY_LEN(&obj->checksums); ++idx) { + trunnel_assert(written <= avail); + result = signed_checksum32_ed25519_encode(ptr, avail - written, TRUNNEL_DYNARRAY_GET(&obj->checksums, idx)); + if (result < 0) + goto fail; /* XXXXXXX !*/ + written += result; ptr += result; + } } @@ -1508,53 +2166,49 @@ consistency_proof_v1_encode(uint8_t *output, const size_t avail, const consisten return result; } -/** As consistency_proof_v1_parse(), but do not allocate the output - * object. +/** As entries_v1_parse(), but do not allocate the output object. */ static ssize_t -consistency_proof_v1_parse_into(consistency_proof_v1_t *obj, const uint8_t *input, const size_t len_in) +entries_v1_parse_into(entries_v1_t *obj, const uint8_t *input, const size_t len_in) { const uint8_t *ptr = input; size_t remaining = len_in; ssize_t result = 0; (void)result; - /* Parse u64 format IN [T_CONSISTENCY_PROOF_V1] */ + /* Parse u64 magic IN [MAGIC_V1] */ CHECK_REMAINING(8, truncated); - obj->format = trunnel_ntohll(trunnel_get_uint64(ptr)); + obj->magic = trunnel_ntohll(trunnel_get_uint64(ptr)); remaining -= 8; ptr += 8; - if (! (obj->format == T_CONSISTENCY_PROOF_V1)) + if (! (obj->magic == MAGIC_V1)) goto fail; - /* Parse struct ed25519_v1 identifier */ - result = ed25519_v1_parse(&obj->identifier, ptr, remaining); - if (result < 0) - goto relay_fail; - trunnel_assert((size_t)result <= remaining); - remaining -= result; ptr += result; - - /* Parse u64 old_size */ - CHECK_REMAINING(8, truncated); - obj->old_size = trunnel_ntohll(trunnel_get_uint64(ptr)); - remaining -= 8; ptr += 8; - - /* Parse u64 new_size */ + /* Parse u64 format IN [T_ENTRIES_V1] */ CHECK_REMAINING(8, truncated); - obj->new_size = trunnel_ntohll(trunnel_get_uint64(ptr)); + obj->format = trunnel_ntohll(trunnel_get_uint64(ptr)); remaining -= 8; ptr += 8; + if (! (obj->format == T_ENTRIES_V1)) + goto fail; - /* Parse u64 length */ + /* Parse u64 n_items */ CHECK_REMAINING(8, truncated); - obj->length = trunnel_ntohll(trunnel_get_uint64(ptr)); + obj->n_items = trunnel_ntohll(trunnel_get_uint64(ptr)); remaining -= 8; ptr += 8; - /* Parse u8 hashes[length] */ - CHECK_REMAINING(obj->length, truncated); - TRUNNEL_DYNARRAY_EXPAND(uint8_t, &obj->hashes, obj->length, {}); - obj->hashes.n_ = obj->length; - if (obj->length) - memcpy(obj->hashes.elts_, ptr, obj->length); - ptr += obj->length; remaining -= obj->length; + /* Parse struct signed_checksum32_ed25519 checksums[n_items] */ + TRUNNEL_DYNARRAY_EXPAND(signed_checksum32_ed25519_t *, &obj->checksums, obj->n_items, {}); + { + signed_checksum32_ed25519_t * elt; + unsigned idx; + for (idx = 0; idx < obj->n_items; ++idx) { + result = signed_checksum32_ed25519_parse(&elt, ptr, remaining); + if (result < 0) + goto relay_fail; + trunnel_assert((size_t)result <= remaining); + remaining -= result; ptr += result; + TRUNNEL_DYNARRAY_ADD(signed_checksum32_ed25519_t *, &obj->checksums, elt, {signed_checksum32_ed25519_free(elt);}); + } + } trunnel_assert(ptr + remaining == input + len_in); return len_in - remaining; @@ -1571,15 +2225,15 @@ consistency_proof_v1_parse_into(consistency_proof_v1_t *obj, const uint8_t *inpu } ssize_t -consistency_proof_v1_parse(consistency_proof_v1_t **output, const uint8_t *input, const size_t len_in) +entries_v1_parse(entries_v1_t **output, const uint8_t *input, const size_t len_in) { ssize_t result; - *output = consistency_proof_v1_new(); + *output = entries_v1_new(); if (NULL == *output) return -1; - result = consistency_proof_v1_parse_into(*output, input, len_in); + result = entries_v1_parse_into(*output, input, len_in); if (result < 0) { - consistency_proof_v1_free(*output); + entries_v1_free(*output); *output = NULL; } return result; @@ -1590,6 +2244,7 @@ inclusion_proof_v1_new(void) inclusion_proof_v1_t *val = trunnel_calloc(1, sizeof(inclusion_proof_v1_t)); if (NULL == val) return NULL; + val->magic = MAGIC_V1; val->format = T_INCLUSION_PROOF_V1; return val; } @@ -1600,8 +2255,13 @@ static void inclusion_proof_v1_clear(inclusion_proof_v1_t *obj) { (void) obj; - ed25519_v1_free(obj->identifier); - obj->identifier = NULL; + { + + unsigned idx; + for (idx = 0; idx < TRUNNEL_DYNARRAY_LEN(&obj->hashes); ++idx) { + hash_free(TRUNNEL_DYNARRAY_GET(&obj->hashes, idx)); + } + } TRUNNEL_DYNARRAY_WIPE(&obj->hashes); TRUNNEL_DYNARRAY_CLEAR(&obj->hashes); } @@ -1616,6 +2276,21 @@ inclusion_proof_v1_free(inclusion_proof_v1_t *obj) trunnel_free_(obj); } +uint64_t +inclusion_proof_v1_get_magic(const inclusion_proof_v1_t *inp) +{ + return inp->magic; +} +int +inclusion_proof_v1_set_magic(inclusion_proof_v1_t *inp, uint64_t val) +{ + if (! ((val == MAGIC_V1))) { + TRUNNEL_SET_ERROR_CODE(inp); + return -1; + } + inp->magic = val; + return 0; +} uint64_t inclusion_proof_v1_get_format(const inclusion_proof_v1_t *inp) { @@ -1631,29 +2306,42 @@ inclusion_proof_v1_set_format(inclusion_proof_v1_t *inp, uint64_t val) inp->format = val; return 0; } -struct ed25519_v1_st * -inclusion_proof_v1_get_identifier(inclusion_proof_v1_t *inp) +size_t +inclusion_proof_v1_getlen_identifier(const inclusion_proof_v1_t *inp) { - return inp->identifier; + (void)inp; return 32; } -const struct ed25519_v1_st * -inclusion_proof_v1_getconst_identifier(const inclusion_proof_v1_t *inp) + +uint8_t +inclusion_proof_v1_get_identifier(inclusion_proof_v1_t *inp, size_t idx) { - return inclusion_proof_v1_get_identifier((inclusion_proof_v1_t*) inp); + trunnel_assert(idx < 32); + return inp->identifier[idx]; } -int -inclusion_proof_v1_set_identifier(inclusion_proof_v1_t *inp, struct ed25519_v1_st *val) + +uint8_t +inclusion_proof_v1_getconst_identifier(const inclusion_proof_v1_t *inp, size_t idx) { - if (inp->identifier && inp->identifier != val) - ed25519_v1_free(inp->identifier); - return inclusion_proof_v1_set0_identifier(inp, val); + return inclusion_proof_v1_get_identifier((inclusion_proof_v1_t*)inp, idx); } int -inclusion_proof_v1_set0_identifier(inclusion_proof_v1_t *inp, struct ed25519_v1_st *val) +inclusion_proof_v1_set_identifier(inclusion_proof_v1_t *inp, size_t idx, uint8_t elt) { - inp->identifier = val; + trunnel_assert(idx < 32); + inp->identifier[idx] = elt; return 0; } + +uint8_t * +inclusion_proof_v1_getarray_identifier(inclusion_proof_v1_t *inp) +{ + return inp->identifier; +} +const uint8_t * +inclusion_proof_v1_getconstarray_identifier(const inclusion_proof_v1_t *inp) +{ + return (const uint8_t *)inclusion_proof_v1_getarray_identifier((inclusion_proof_v1_t*)inp); +} uint64_t inclusion_proof_v1_get_tree_size(const inclusion_proof_v1_t *inp) { @@ -1677,14 +2365,14 @@ inclusion_proof_v1_set_leaf_index(inclusion_proof_v1_t *inp, uint64_t val) return 0; } uint64_t -inclusion_proof_v1_get_length(const inclusion_proof_v1_t *inp) +inclusion_proof_v1_get_n_items(const inclusion_proof_v1_t *inp) { - return inp->length; + return inp->n_items; } int -inclusion_proof_v1_set_length(inclusion_proof_v1_t *inp, uint64_t val) +inclusion_proof_v1_set_n_items(inclusion_proof_v1_t *inp, uint64_t val) { - inp->length = val; + inp->n_items = val; return 0; } size_t @@ -1693,58 +2381,66 @@ inclusion_proof_v1_getlen_hashes(const inclusion_proof_v1_t *inp) return TRUNNEL_DYNARRAY_LEN(&inp->hashes); } -uint8_t +struct hash_st * inclusion_proof_v1_get_hashes(inclusion_proof_v1_t *inp, size_t idx) { return TRUNNEL_DYNARRAY_GET(&inp->hashes, idx); } -uint8_t + const struct hash_st * inclusion_proof_v1_getconst_hashes(const inclusion_proof_v1_t *inp, size_t idx) { return inclusion_proof_v1_get_hashes((inclusion_proof_v1_t*)inp, idx); } int -inclusion_proof_v1_set_hashes(inclusion_proof_v1_t *inp, size_t idx, uint8_t elt) +inclusion_proof_v1_set_hashes(inclusion_proof_v1_t *inp, size_t idx, struct hash_st * elt) +{ + hash_t *oldval = TRUNNEL_DYNARRAY_GET(&inp->hashes, idx); + if (oldval && oldval != elt) + hash_free(oldval); + return inclusion_proof_v1_set0_hashes(inp, idx, elt); +} +int +inclusion_proof_v1_set0_hashes(inclusion_proof_v1_t *inp, size_t idx, struct hash_st * elt) { TRUNNEL_DYNARRAY_SET(&inp->hashes, idx, elt); return 0; } int -inclusion_proof_v1_add_hashes(inclusion_proof_v1_t *inp, uint8_t elt) +inclusion_proof_v1_add_hashes(inclusion_proof_v1_t *inp, struct hash_st * elt) { #if SIZE_MAX >= UINT64_MAX if (inp->hashes.n_ == UINT64_MAX) goto trunnel_alloc_failed; #endif - TRUNNEL_DYNARRAY_ADD(uint8_t, &inp->hashes, elt, {}); + TRUNNEL_DYNARRAY_ADD(struct hash_st *, &inp->hashes, elt, {}); return 0; trunnel_alloc_failed: TRUNNEL_SET_ERROR_CODE(inp); return -1; } -uint8_t * +struct hash_st * * inclusion_proof_v1_getarray_hashes(inclusion_proof_v1_t *inp) { return inp->hashes.elts_; } -const uint8_t * +const struct hash_st * const * inclusion_proof_v1_getconstarray_hashes(const inclusion_proof_v1_t *inp) { - return (const uint8_t *)inclusion_proof_v1_getarray_hashes((inclusion_proof_v1_t*)inp); + return (const struct hash_st * const *)inclusion_proof_v1_getarray_hashes((inclusion_proof_v1_t*)inp); } int inclusion_proof_v1_setlen_hashes(inclusion_proof_v1_t *inp, size_t newlen) { - uint8_t *newptr; + struct hash_st * *newptr; #if UINT64_MAX < SIZE_MAX if (newlen > UINT64_MAX) goto trunnel_alloc_failed; #endif newptr = trunnel_dynarray_setlen(&inp->hashes.allocated_, &inp->hashes.n_, inp->hashes.elts_, newlen, - sizeof(inp->hashes.elts_[0]), (trunnel_free_fn_t) NULL, + sizeof(inp->hashes.elts_[0]), (trunnel_free_fn_t) hash_free, &inp->trunnel_error_code_); if (newlen != 0 && newptr == NULL) goto trunnel_alloc_failed; @@ -1761,14 +2457,20 @@ inclusion_proof_v1_check(const inclusion_proof_v1_t *obj) return "Object was NULL"; if (obj->trunnel_error_code_) return "A set function failed on this object"; + if (! (obj->magic == MAGIC_V1)) + return "Integer out of bounds"; if (! (obj->format == T_INCLUSION_PROOF_V1)) return "Integer out of bounds"; { const char *msg; - if (NULL != (msg = ed25519_v1_check(obj->identifier))) - return msg; + + unsigned idx; + for (idx = 0; idx < TRUNNEL_DYNARRAY_LEN(&obj->hashes); ++idx) { + if (NULL != (msg = hash_check(TRUNNEL_DYNARRAY_GET(&obj->hashes, idx)))) + return msg; + } } - if (TRUNNEL_DYNARRAY_LEN(&obj->hashes) != obj->length) + if (TRUNNEL_DYNARRAY_LEN(&obj->hashes) != obj->n_items) return "Length mismatch for hashes"; return NULL; } @@ -1782,11 +2484,14 @@ inclusion_proof_v1_encoded_len(const inclusion_proof_v1_t *obj) return -1; + /* Length of u64 magic IN [MAGIC_V1] */ + result += 8; + /* Length of u64 format IN [T_INCLUSION_PROOF_V1] */ result += 8; - /* Length of struct ed25519_v1 identifier */ - result += ed25519_v1_encoded_len(obj->identifier); + /* Length of u8 identifier[32] */ + result += 32; /* Length of u64 tree_size */ result += 8; @@ -1794,11 +2499,17 @@ inclusion_proof_v1_encoded_len(const inclusion_proof_v1_t *obj) /* Length of u64 leaf_index */ result += 8; - /* Length of u64 length */ + /* Length of u64 n_items */ result += 8; - /* Length of u8 hashes[length] */ - result += TRUNNEL_DYNARRAY_LEN(&obj->hashes); + /* Length of struct hash hashes[n_items] */ + { + + unsigned idx; + for (idx = 0; idx < TRUNNEL_DYNARRAY_LEN(&obj->hashes); ++idx) { + result += hash_encoded_len(TRUNNEL_DYNARRAY_GET(&obj->hashes, idx)); + } + } return result; } int @@ -1826,6 +2537,13 @@ inclusion_proof_v1_encode(uint8_t *output, const size_t avail, const inclusion_p trunnel_assert(encoded_len >= 0); #endif + /* Encode u64 magic IN [MAGIC_V1] */ + trunnel_assert(written <= avail); + if (avail - written < 8) + goto truncated; + trunnel_set_uint64(ptr, trunnel_htonll(obj->magic)); + written += 8; ptr += 8; + /* Encode u64 format IN [T_INCLUSION_PROOF_V1] */ trunnel_assert(written <= avail); if (avail - written < 8) @@ -1833,12 +2551,12 @@ inclusion_proof_v1_encode(uint8_t *output, const size_t avail, const inclusion_p trunnel_set_uint64(ptr, trunnel_htonll(obj->format)); written += 8; ptr += 8; - /* Encode struct ed25519_v1 identifier */ + /* Encode u8 identifier[32] */ trunnel_assert(written <= avail); - result = ed25519_v1_encode(ptr, avail - written, obj->identifier); - if (result < 0) - goto fail; /* XXXXXXX !*/ - written += result; ptr += result; + if (avail - written < 32) + goto truncated; + memcpy(ptr, obj->identifier, 32); + written += 32; ptr += 32; /* Encode u64 tree_size */ trunnel_assert(written <= avail); @@ -1854,23 +2572,24 @@ inclusion_proof_v1_encode(uint8_t *output, const size_t avail, const inclusion_p trunnel_set_uint64(ptr, trunnel_htonll(obj->leaf_index)); written += 8; ptr += 8; - /* Encode u64 length */ + /* Encode u64 n_items */ trunnel_assert(written <= avail); if (avail - written < 8) goto truncated; - trunnel_set_uint64(ptr, trunnel_htonll(obj->length)); + trunnel_set_uint64(ptr, trunnel_htonll(obj->n_items)); written += 8; ptr += 8; - /* Encode u8 hashes[length] */ + /* Encode struct hash hashes[n_items] */ { - size_t elt_len = TRUNNEL_DYNARRAY_LEN(&obj->hashes); - trunnel_assert(obj->length == elt_len); - trunnel_assert(written <= avail); - if (avail - written < elt_len) - goto truncated; - if (elt_len) - memcpy(ptr, obj->hashes.elts_, elt_len); - written += elt_len; ptr += elt_len; + + unsigned idx; + for (idx = 0; idx < TRUNNEL_DYNARRAY_LEN(&obj->hashes); ++idx) { + trunnel_assert(written <= avail); + result = hash_encode(ptr, avail - written, TRUNNEL_DYNARRAY_GET(&obj->hashes, idx)); + if (result < 0) + goto fail; /* XXXXXXX !*/ + written += result; ptr += result; + } } @@ -1908,6 +2627,13 @@ inclusion_proof_v1_parse_into(inclusion_proof_v1_t *obj, const uint8_t *input, c ssize_t result = 0; (void)result; + /* Parse u64 magic IN [MAGIC_V1] */ + CHECK_REMAINING(8, truncated); + obj->magic = trunnel_ntohll(trunnel_get_uint64(ptr)); + remaining -= 8; ptr += 8; + if (! (obj->magic == MAGIC_V1)) + goto fail; + /* Parse u64 format IN [T_INCLUSION_PROOF_V1] */ CHECK_REMAINING(8, truncated); obj->format = trunnel_ntohll(trunnel_get_uint64(ptr)); @@ -1915,12 +2641,10 @@ inclusion_proof_v1_parse_into(inclusion_proof_v1_t *obj, const uint8_t *input, c if (! (obj->format == T_INCLUSION_PROOF_V1)) goto fail; - /* Parse struct ed25519_v1 identifier */ - result = ed25519_v1_parse(&obj->identifier, ptr, remaining); - if (result < 0) - goto relay_fail; - trunnel_assert((size_t)result <= remaining); - remaining -= result; ptr += result; + /* Parse u8 identifier[32] */ + CHECK_REMAINING(32, truncated); + memcpy(obj->identifier, ptr, 32); + remaining -= 32; ptr += 32; /* Parse u64 tree_size */ CHECK_REMAINING(8, truncated); @@ -1932,18 +2656,25 @@ inclusion_proof_v1_parse_into(inclusion_proof_v1_t *obj, const uint8_t *input, c obj->leaf_index = trunnel_ntohll(trunnel_get_uint64(ptr)); remaining -= 8; ptr += 8; - /* Parse u64 length */ + /* Parse u64 n_items */ CHECK_REMAINING(8, truncated); - obj->length = trunnel_ntohll(trunnel_get_uint64(ptr)); + obj->n_items = trunnel_ntohll(trunnel_get_uint64(ptr)); remaining -= 8; ptr += 8; - /* Parse u8 hashes[length] */ - CHECK_REMAINING(obj->length, truncated); - TRUNNEL_DYNARRAY_EXPAND(uint8_t, &obj->hashes, obj->length, {}); - obj->hashes.n_ = obj->length; - if (obj->length) - memcpy(obj->hashes.elts_, ptr, obj->length); - ptr += obj->length; remaining -= obj->length; + /* Parse struct hash hashes[n_items] */ + TRUNNEL_DYNARRAY_EXPAND(hash_t *, &obj->hashes, obj->n_items, {}); + { + hash_t * elt; + unsigned idx; + for (idx = 0; idx < obj->n_items; ++idx) { + result = hash_parse(&elt, ptr, remaining); + if (result < 0) + goto relay_fail; + trunnel_assert((size_t)result <= remaining); + remaining -= result; ptr += result; + TRUNNEL_DYNARRAY_ADD(hash_t *, &obj->hashes, elt, {hash_free(elt);}); + } + } trunnel_assert(ptr + remaining == input + len_in); return len_in - remaining; @@ -1979,6 +2710,7 @@ request_v1_new(void) request_v1_t *val = trunnel_calloc(1, sizeof(request_v1_t)); if (NULL == val) return NULL; + val->magic = MAGIC_V1; val->format = T_GET_CONSISTENCY_PROOF_V1; return val; } @@ -2007,6 +2739,21 @@ request_v1_free(request_v1_t *obj) trunnel_free_(obj); } +uint64_t +request_v1_get_magic(const request_v1_t *inp) +{ + return inp->magic; +} +int +request_v1_set_magic(request_v1_t *inp, uint64_t val) +{ + if (! ((val == MAGIC_V1))) { + TRUNNEL_SET_ERROR_CODE(inp); + return -1; + } + inp->magic = val; + return 0; +} uint64_t request_v1_get_format(const request_v1_t *inp) { @@ -2098,6 +2845,8 @@ request_v1_check(const request_v1_t *obj) return "Object was NULL"; if (obj->trunnel_error_code_) return "A set function failed on this object"; + if (! (obj->magic == MAGIC_V1)) + return "Integer out of bounds"; if (! (obj->format == T_GET_CONSISTENCY_PROOF_V1 || obj->format == T_GET_ENTRIES_V1 || obj->format == T_GET_PROOF_BY_HASH_V1)) return "Integer out of bounds"; switch (obj->format) { @@ -2142,6 +2891,9 @@ request_v1_encoded_len(const request_v1_t *obj) return -1; + /* Length of u64 magic IN [MAGIC_V1] */ + result += 8; + /* Length of u64 format IN [T_GET_CONSISTENCY_PROOF_V1, T_GET_ENTRIES_V1, T_GET_PROOF_BY_HASH_V1] */ result += 8; switch (obj->format) { @@ -2195,6 +2947,13 @@ request_v1_encode(uint8_t *output, const size_t avail, const request_v1_t *obj) trunnel_assert(encoded_len >= 0); #endif + /* Encode u64 magic IN [MAGIC_V1] */ + trunnel_assert(written <= avail); + if (avail - written < 8) + goto truncated; + trunnel_set_uint64(ptr, trunnel_htonll(obj->magic)); + written += 8; ptr += 8; + /* Encode u64 format IN [T_GET_CONSISTENCY_PROOF_V1, T_GET_ENTRIES_V1, T_GET_PROOF_BY_HASH_V1] */ trunnel_assert(written <= avail); if (avail - written < 8) @@ -2275,6 +3034,13 @@ request_v1_parse_into(request_v1_t *obj, const uint8_t *input, const size_t len_ ssize_t result = 0; (void)result; + /* Parse u64 magic IN [MAGIC_V1] */ + CHECK_REMAINING(8, truncated); + obj->magic = trunnel_ntohll(trunnel_get_uint64(ptr)); + remaining -= 8; ptr += 8; + if (! (obj->magic == MAGIC_V1)) + goto fail; + /* Parse u64 format IN [T_GET_CONSISTENCY_PROOF_V1, T_GET_ENTRIES_V1, T_GET_PROOF_BY_HASH_V1] */ CHECK_REMAINING(8, truncated); obj->format = trunnel_ntohll(trunnel_get_uint64(ptr)); @@ -2346,351 +3112,359 @@ request_v1_parse(request_v1_t **output, const uint8_t *input, const size_t len_i } return result; } -signed_checksum32_ed25519_v1_t * -signed_checksum32_ed25519_v1_new(void) +signed_tree_head_v1_t * +signed_tree_head_v1_new(void) { - signed_checksum32_ed25519_v1_t *val = trunnel_calloc(1, sizeof(signed_checksum32_ed25519_v1_t)); + signed_tree_head_v1_t *val = trunnel_calloc(1, sizeof(signed_tree_head_v1_t)); if (NULL == val) return NULL; - val->format = T_SIGNED_CHECKSUM32_ED25519_V1; - val->length = 1; + val->magic = MAGIC_V1; + val->format = T_SIGNED_TREE_HEAD_V1; return val; } /** Release all storage held inside 'obj', but do not free 'obj'. */ static void -signed_checksum32_ed25519_v1_clear(signed_checksum32_ed25519_v1_t *obj) +signed_tree_head_v1_clear(signed_tree_head_v1_t *obj) { (void) obj; - TRUNNEL_DYNARRAY_WIPE(&obj->identifier); - TRUNNEL_DYNARRAY_CLEAR(&obj->identifier); - ed25519_v1_free(obj->namespace); - obj->namespace = NULL; + { + + unsigned idx; + for (idx = 0; idx < TRUNNEL_DYNARRAY_LEN(&obj->signatures); ++idx) { + sigident_ed25519_free(TRUNNEL_DYNARRAY_GET(&obj->signatures, idx)); + } + } + TRUNNEL_DYNARRAY_WIPE(&obj->signatures); + TRUNNEL_DYNARRAY_CLEAR(&obj->signatures); } void -signed_checksum32_ed25519_v1_free(signed_checksum32_ed25519_v1_t *obj) +signed_tree_head_v1_free(signed_tree_head_v1_t *obj) { if (obj == NULL) return; - signed_checksum32_ed25519_v1_clear(obj); - trunnel_memwipe(obj, sizeof(signed_checksum32_ed25519_v1_t)); + signed_tree_head_v1_clear(obj); + trunnel_memwipe(obj, sizeof(signed_tree_head_v1_t)); trunnel_free_(obj); } uint64_t -signed_checksum32_ed25519_v1_get_format(const signed_checksum32_ed25519_v1_t *inp) +signed_tree_head_v1_get_magic(const signed_tree_head_v1_t *inp) +{ + return inp->magic; +} +int +signed_tree_head_v1_set_magic(signed_tree_head_v1_t *inp, uint64_t val) +{ + if (! ((val == MAGIC_V1))) { + TRUNNEL_SET_ERROR_CODE(inp); + return -1; + } + inp->magic = val; + return 0; +} +uint64_t +signed_tree_head_v1_get_format(const signed_tree_head_v1_t *inp) { return inp->format; } int -signed_checksum32_ed25519_v1_set_format(signed_checksum32_ed25519_v1_t *inp, uint64_t val) +signed_tree_head_v1_set_format(signed_tree_head_v1_t *inp, uint64_t val) { - if (! ((val == T_SIGNED_CHECKSUM32_ED25519_V1))) { + if (! ((val == T_SIGNED_TREE_HEAD_V1))) { TRUNNEL_SET_ERROR_CODE(inp); return -1; } inp->format = val; return 0; } +uint64_t +signed_tree_head_v1_get_timestamp(const signed_tree_head_v1_t *inp) +{ + return inp->timestamp; +} +int +signed_tree_head_v1_set_timestamp(signed_tree_head_v1_t *inp, uint64_t val) +{ + inp->timestamp = val; + return 0; +} +uint64_t +signed_tree_head_v1_get_tree_size(const signed_tree_head_v1_t *inp) +{ + return inp->tree_size; +} +int +signed_tree_head_v1_set_tree_size(signed_tree_head_v1_t *inp, uint64_t val) +{ + inp->tree_size = val; + return 0; +} size_t -signed_checksum32_ed25519_v1_getlen_checksum(const signed_checksum32_ed25519_v1_t *inp) +signed_tree_head_v1_getlen_root_hash(const signed_tree_head_v1_t *inp) { (void)inp; return 32; } uint8_t -signed_checksum32_ed25519_v1_get_checksum(signed_checksum32_ed25519_v1_t *inp, size_t idx) +signed_tree_head_v1_get_root_hash(signed_tree_head_v1_t *inp, size_t idx) { trunnel_assert(idx < 32); - return inp->checksum[idx]; + return inp->root_hash[idx]; } uint8_t -signed_checksum32_ed25519_v1_getconst_checksum(const signed_checksum32_ed25519_v1_t *inp, size_t idx) +signed_tree_head_v1_getconst_root_hash(const signed_tree_head_v1_t *inp, size_t idx) { - return signed_checksum32_ed25519_v1_get_checksum((signed_checksum32_ed25519_v1_t*)inp, idx); + return signed_tree_head_v1_get_root_hash((signed_tree_head_v1_t*)inp, idx); } int -signed_checksum32_ed25519_v1_set_checksum(signed_checksum32_ed25519_v1_t *inp, size_t idx, uint8_t elt) +signed_tree_head_v1_set_root_hash(signed_tree_head_v1_t *inp, size_t idx, uint8_t elt) { trunnel_assert(idx < 32); - inp->checksum[idx] = elt; + inp->root_hash[idx] = elt; return 0; } uint8_t * -signed_checksum32_ed25519_v1_getarray_checksum(signed_checksum32_ed25519_v1_t *inp) +signed_tree_head_v1_getarray_root_hash(signed_tree_head_v1_t *inp) { - return inp->checksum; + return inp->root_hash; } const uint8_t * -signed_checksum32_ed25519_v1_getconstarray_checksum(const signed_checksum32_ed25519_v1_t *inp) +signed_tree_head_v1_getconstarray_root_hash(const signed_tree_head_v1_t *inp) { - return (const uint8_t *)signed_checksum32_ed25519_v1_getarray_checksum((signed_checksum32_ed25519_v1_t*)inp); + return (const uint8_t *)signed_tree_head_v1_getarray_root_hash((signed_tree_head_v1_t*)inp); } uint64_t -signed_checksum32_ed25519_v1_get_length(const signed_checksum32_ed25519_v1_t *inp) +signed_tree_head_v1_get_n_items(const signed_tree_head_v1_t *inp) { - return inp->length; + return inp->n_items; } int -signed_checksum32_ed25519_v1_set_length(signed_checksum32_ed25519_v1_t *inp, uint64_t val) +signed_tree_head_v1_set_n_items(signed_tree_head_v1_t *inp, uint64_t val) { - if (! (((val >= 1 && val <= 127)))) { - TRUNNEL_SET_ERROR_CODE(inp); - return -1; - } - inp->length = val; + inp->n_items = val; return 0; } size_t -signed_checksum32_ed25519_v1_getlen_identifier(const signed_checksum32_ed25519_v1_t *inp) +signed_tree_head_v1_getlen_signatures(const signed_tree_head_v1_t *inp) { - return TRUNNEL_DYNARRAY_LEN(&inp->identifier); + return TRUNNEL_DYNARRAY_LEN(&inp->signatures); } -uint8_t -signed_checksum32_ed25519_v1_get_identifier(signed_checksum32_ed25519_v1_t *inp, size_t idx) +struct sigident_ed25519_st * +signed_tree_head_v1_get_signatures(signed_tree_head_v1_t *inp, size_t idx) { - return TRUNNEL_DYNARRAY_GET(&inp->identifier, idx); + return TRUNNEL_DYNARRAY_GET(&inp->signatures, idx); } -uint8_t -signed_checksum32_ed25519_v1_getconst_identifier(const signed_checksum32_ed25519_v1_t *inp, size_t idx) + const struct sigident_ed25519_st * +signed_tree_head_v1_getconst_signatures(const signed_tree_head_v1_t *inp, size_t idx) { - return signed_checksum32_ed25519_v1_get_identifier((signed_checksum32_ed25519_v1_t*)inp, idx); + return signed_tree_head_v1_get_signatures((signed_tree_head_v1_t*)inp, idx); } int -signed_checksum32_ed25519_v1_set_identifier(signed_checksum32_ed25519_v1_t *inp, size_t idx, uint8_t elt) +signed_tree_head_v1_set_signatures(signed_tree_head_v1_t *inp, size_t idx, struct sigident_ed25519_st * elt) { - TRUNNEL_DYNARRAY_SET(&inp->identifier, idx, elt); + sigident_ed25519_t *oldval = TRUNNEL_DYNARRAY_GET(&inp->signatures, idx); + if (oldval && oldval != elt) + sigident_ed25519_free(oldval); + return signed_tree_head_v1_set0_signatures(inp, idx, elt); +} +int +signed_tree_head_v1_set0_signatures(signed_tree_head_v1_t *inp, size_t idx, struct sigident_ed25519_st * elt) +{ + TRUNNEL_DYNARRAY_SET(&inp->signatures, idx, elt); return 0; } int -signed_checksum32_ed25519_v1_add_identifier(signed_checksum32_ed25519_v1_t *inp, uint8_t elt) +signed_tree_head_v1_add_signatures(signed_tree_head_v1_t *inp, struct sigident_ed25519_st * elt) { #if SIZE_MAX >= UINT64_MAX - if (inp->identifier.n_ == UINT64_MAX) + if (inp->signatures.n_ == UINT64_MAX) goto trunnel_alloc_failed; #endif - TRUNNEL_DYNARRAY_ADD(uint8_t, &inp->identifier, elt, {}); + TRUNNEL_DYNARRAY_ADD(struct sigident_ed25519_st *, &inp->signatures, elt, {}); return 0; trunnel_alloc_failed: TRUNNEL_SET_ERROR_CODE(inp); return -1; } -uint8_t * -signed_checksum32_ed25519_v1_getarray_identifier(signed_checksum32_ed25519_v1_t *inp) +struct sigident_ed25519_st * * +signed_tree_head_v1_getarray_signatures(signed_tree_head_v1_t *inp) { - return inp->identifier.elts_; + return inp->signatures.elts_; } -const uint8_t * -signed_checksum32_ed25519_v1_getconstarray_identifier(const signed_checksum32_ed25519_v1_t *inp) +const struct sigident_ed25519_st * const * +signed_tree_head_v1_getconstarray_signatures(const signed_tree_head_v1_t *inp) { - return (const uint8_t *)signed_checksum32_ed25519_v1_getarray_identifier((signed_checksum32_ed25519_v1_t*)inp); + return (const struct sigident_ed25519_st * const *)signed_tree_head_v1_getarray_signatures((signed_tree_head_v1_t*)inp); } int -signed_checksum32_ed25519_v1_setlen_identifier(signed_checksum32_ed25519_v1_t *inp, size_t newlen) +signed_tree_head_v1_setlen_signatures(signed_tree_head_v1_t *inp, size_t newlen) { - uint8_t *newptr; + struct sigident_ed25519_st * *newptr; #if UINT64_MAX < SIZE_MAX if (newlen > UINT64_MAX) goto trunnel_alloc_failed; #endif - newptr = trunnel_dynarray_setlen(&inp->identifier.allocated_, - &inp->identifier.n_, inp->identifier.elts_, newlen, - sizeof(inp->identifier.elts_[0]), (trunnel_free_fn_t) NULL, + newptr = trunnel_dynarray_setlen(&inp->signatures.allocated_, + &inp->signatures.n_, inp->signatures.elts_, newlen, + sizeof(inp->signatures.elts_[0]), (trunnel_free_fn_t) sigident_ed25519_free, &inp->trunnel_error_code_); if (newlen != 0 && newptr == NULL) goto trunnel_alloc_failed; - inp->identifier.elts_ = newptr; + inp->signatures.elts_ = newptr; return 0; trunnel_alloc_failed: TRUNNEL_SET_ERROR_CODE(inp); return -1; } -size_t -signed_checksum32_ed25519_v1_getlen_signature(const signed_checksum32_ed25519_v1_t *inp) -{ - (void)inp; return 64; -} - -uint8_t -signed_checksum32_ed25519_v1_get_signature(signed_checksum32_ed25519_v1_t *inp, size_t idx) -{ - trunnel_assert(idx < 64); - return inp->signature[idx]; -} - -uint8_t -signed_checksum32_ed25519_v1_getconst_signature(const signed_checksum32_ed25519_v1_t *inp, size_t idx) -{ - return signed_checksum32_ed25519_v1_get_signature((signed_checksum32_ed25519_v1_t*)inp, idx); -} -int -signed_checksum32_ed25519_v1_set_signature(signed_checksum32_ed25519_v1_t *inp, size_t idx, uint8_t elt) -{ - trunnel_assert(idx < 64); - inp->signature[idx] = elt; - return 0; -} - -uint8_t * -signed_checksum32_ed25519_v1_getarray_signature(signed_checksum32_ed25519_v1_t *inp) -{ - return inp->signature; -} -const uint8_t * -signed_checksum32_ed25519_v1_getconstarray_signature(const signed_checksum32_ed25519_v1_t *inp) -{ - return (const uint8_t *)signed_checksum32_ed25519_v1_getarray_signature((signed_checksum32_ed25519_v1_t*)inp); -} -struct ed25519_v1_st * -signed_checksum32_ed25519_v1_get_namespace(signed_checksum32_ed25519_v1_t *inp) -{ - return inp->namespace; -} -const struct ed25519_v1_st * -signed_checksum32_ed25519_v1_getconst_namespace(const signed_checksum32_ed25519_v1_t *inp) -{ - return signed_checksum32_ed25519_v1_get_namespace((signed_checksum32_ed25519_v1_t*) inp); -} -int -signed_checksum32_ed25519_v1_set_namespace(signed_checksum32_ed25519_v1_t *inp, struct ed25519_v1_st *val) -{ - if (inp->namespace && inp->namespace != val) - ed25519_v1_free(inp->namespace); - return signed_checksum32_ed25519_v1_set0_namespace(inp, val); -} -int -signed_checksum32_ed25519_v1_set0_namespace(signed_checksum32_ed25519_v1_t *inp, struct ed25519_v1_st *val) -{ - inp->namespace = val; - return 0; -} const char * -signed_checksum32_ed25519_v1_check(const signed_checksum32_ed25519_v1_t *obj) +signed_tree_head_v1_check(const signed_tree_head_v1_t *obj) { if (obj == NULL) return "Object was NULL"; if (obj->trunnel_error_code_) return "A set function failed on this object"; - if (! (obj->format == T_SIGNED_CHECKSUM32_ED25519_V1)) + if (! (obj->magic == MAGIC_V1)) return "Integer out of bounds"; - if (! ((obj->length >= 1 && obj->length <= 127))) + if (! (obj->format == T_SIGNED_TREE_HEAD_V1)) return "Integer out of bounds"; - if (TRUNNEL_DYNARRAY_LEN(&obj->identifier) != obj->length) - return "Length mismatch for identifier"; { const char *msg; - if (NULL != (msg = ed25519_v1_check(obj->namespace))) - return msg; + + unsigned idx; + for (idx = 0; idx < TRUNNEL_DYNARRAY_LEN(&obj->signatures); ++idx) { + if (NULL != (msg = sigident_ed25519_check(TRUNNEL_DYNARRAY_GET(&obj->signatures, idx)))) + return msg; + } } + if (TRUNNEL_DYNARRAY_LEN(&obj->signatures) != obj->n_items) + return "Length mismatch for signatures"; return NULL; } ssize_t -signed_checksum32_ed25519_v1_encoded_len(const signed_checksum32_ed25519_v1_t *obj) +signed_tree_head_v1_encoded_len(const signed_tree_head_v1_t *obj) { ssize_t result = 0; - if (NULL != signed_checksum32_ed25519_v1_check(obj)) + if (NULL != signed_tree_head_v1_check(obj)) return -1; - /* Length of u64 format IN [T_SIGNED_CHECKSUM32_ED25519_V1] */ + /* Length of u64 magic IN [MAGIC_V1] */ result += 8; - /* Length of u8 checksum[32] */ - result += 32; + /* Length of u64 format IN [T_SIGNED_TREE_HEAD_V1] */ + result += 8; - /* Length of u64 length IN [1..127] */ + /* Length of u64 timestamp */ result += 8; - /* Length of u8 identifier[length] */ - result += TRUNNEL_DYNARRAY_LEN(&obj->identifier); + /* Length of u64 tree_size */ + result += 8; - /* Length of u8 signature[64] */ - result += 64; + /* Length of u8 root_hash[32] */ + result += 32; + + /* Length of u64 n_items */ + result += 8; - /* Length of struct ed25519_v1 namespace */ - result += ed25519_v1_encoded_len(obj->namespace); + /* Length of struct sigident_ed25519 signatures[n_items] */ + { + + unsigned idx; + for (idx = 0; idx < TRUNNEL_DYNARRAY_LEN(&obj->signatures); ++idx) { + result += sigident_ed25519_encoded_len(TRUNNEL_DYNARRAY_GET(&obj->signatures, idx)); + } + } return result; } int -signed_checksum32_ed25519_v1_clear_errors(signed_checksum32_ed25519_v1_t *obj) +signed_tree_head_v1_clear_errors(signed_tree_head_v1_t *obj) { int r = obj->trunnel_error_code_; obj->trunnel_error_code_ = 0; return r; } ssize_t -signed_checksum32_ed25519_v1_encode(uint8_t *output, const size_t avail, const signed_checksum32_ed25519_v1_t *obj) +signed_tree_head_v1_encode(uint8_t *output, const size_t avail, const signed_tree_head_v1_t *obj) { ssize_t result = 0; size_t written = 0; uint8_t *ptr = output; const char *msg; #ifdef TRUNNEL_CHECK_ENCODED_LEN - const ssize_t encoded_len = signed_checksum32_ed25519_v1_encoded_len(obj); + const ssize_t encoded_len = signed_tree_head_v1_encoded_len(obj); #endif - if (NULL != (msg = signed_checksum32_ed25519_v1_check(obj))) + if (NULL != (msg = signed_tree_head_v1_check(obj))) goto check_failed; #ifdef TRUNNEL_CHECK_ENCODED_LEN trunnel_assert(encoded_len >= 0); #endif - /* Encode u64 format IN [T_SIGNED_CHECKSUM32_ED25519_V1] */ + /* Encode u64 magic IN [MAGIC_V1] */ trunnel_assert(written <= avail); if (avail - written < 8) goto truncated; - trunnel_set_uint64(ptr, trunnel_htonll(obj->format)); + trunnel_set_uint64(ptr, trunnel_htonll(obj->magic)); written += 8; ptr += 8; - /* Encode u8 checksum[32] */ + /* Encode u64 format IN [T_SIGNED_TREE_HEAD_V1] */ trunnel_assert(written <= avail); - if (avail - written < 32) + if (avail - written < 8) goto truncated; - memcpy(ptr, obj->checksum, 32); - written += 32; ptr += 32; + trunnel_set_uint64(ptr, trunnel_htonll(obj->format)); + written += 8; ptr += 8; - /* Encode u64 length IN [1..127] */ + /* Encode u64 timestamp */ trunnel_assert(written <= avail); if (avail - written < 8) goto truncated; - trunnel_set_uint64(ptr, trunnel_htonll(obj->length)); + trunnel_set_uint64(ptr, trunnel_htonll(obj->timestamp)); written += 8; ptr += 8; - /* Encode u8 identifier[length] */ - { - size_t elt_len = TRUNNEL_DYNARRAY_LEN(&obj->identifier); - trunnel_assert(obj->length == elt_len); - trunnel_assert(written <= avail); - if (avail - written < elt_len) - goto truncated; - if (elt_len) - memcpy(ptr, obj->identifier.elts_, elt_len); - written += elt_len; ptr += elt_len; - } + /* Encode u64 tree_size */ + trunnel_assert(written <= avail); + if (avail - written < 8) + goto truncated; + trunnel_set_uint64(ptr, trunnel_htonll(obj->tree_size)); + written += 8; ptr += 8; - /* Encode u8 signature[64] */ + /* Encode u8 root_hash[32] */ trunnel_assert(written <= avail); - if (avail - written < 64) + if (avail - written < 32) goto truncated; - memcpy(ptr, obj->signature, 64); - written += 64; ptr += 64; + memcpy(ptr, obj->root_hash, 32); + written += 32; ptr += 32; - /* Encode struct ed25519_v1 namespace */ + /* Encode u64 n_items */ trunnel_assert(written <= avail); - result = ed25519_v1_encode(ptr, avail - written, obj->namespace); - if (result < 0) - goto fail; /* XXXXXXX !*/ - written += result; ptr += result; + if (avail - written < 8) + goto truncated; + trunnel_set_uint64(ptr, trunnel_htonll(obj->n_items)); + written += 8; ptr += 8; + + /* Encode struct sigident_ed25519 signatures[n_items] */ + { + + unsigned idx; + for (idx = 0; idx < TRUNNEL_DYNARRAY_LEN(&obj->signatures); ++idx) { + trunnel_assert(written <= avail); + result = sigident_ed25519_encode(ptr, avail - written, TRUNNEL_DYNARRAY_GET(&obj->signatures, idx)); + if (result < 0) + goto fail; /* XXXXXXX !*/ + written += result; ptr += result; + } + } trunnel_assert(ptr == output + written); @@ -2716,55 +3490,65 @@ signed_checksum32_ed25519_v1_encode(uint8_t *output, const size_t avail, const s return result; } -/** As signed_checksum32_ed25519_v1_parse(), but do not allocate the - * output object. +/** As signed_tree_head_v1_parse(), but do not allocate the output + * object. */ static ssize_t -signed_checksum32_ed25519_v1_parse_into(signed_checksum32_ed25519_v1_t *obj, const uint8_t *input, const size_t len_in) +signed_tree_head_v1_parse_into(signed_tree_head_v1_t *obj, const uint8_t *input, const size_t len_in) { const uint8_t *ptr = input; size_t remaining = len_in; ssize_t result = 0; (void)result; - /* Parse u64 format IN [T_SIGNED_CHECKSUM32_ED25519_V1] */ + /* Parse u64 magic IN [MAGIC_V1] */ + CHECK_REMAINING(8, truncated); + obj->magic = trunnel_ntohll(trunnel_get_uint64(ptr)); + remaining -= 8; ptr += 8; + if (! (obj->magic == MAGIC_V1)) + goto fail; + + /* Parse u64 format IN [T_SIGNED_TREE_HEAD_V1] */ CHECK_REMAINING(8, truncated); obj->format = trunnel_ntohll(trunnel_get_uint64(ptr)); remaining -= 8; ptr += 8; - if (! (obj->format == T_SIGNED_CHECKSUM32_ED25519_V1)) + if (! (obj->format == T_SIGNED_TREE_HEAD_V1)) goto fail; - /* Parse u8 checksum[32] */ - CHECK_REMAINING(32, truncated); - memcpy(obj->checksum, ptr, 32); - remaining -= 32; ptr += 32; + /* Parse u64 timestamp */ + CHECK_REMAINING(8, truncated); + obj->timestamp = trunnel_ntohll(trunnel_get_uint64(ptr)); + remaining -= 8; ptr += 8; - /* Parse u64 length IN [1..127] */ + /* Parse u64 tree_size */ CHECK_REMAINING(8, truncated); - obj->length = trunnel_ntohll(trunnel_get_uint64(ptr)); + obj->tree_size = trunnel_ntohll(trunnel_get_uint64(ptr)); remaining -= 8; ptr += 8; - if (! ((obj->length >= 1 && obj->length <= 127))) - goto fail; - /* Parse u8 identifier[length] */ - CHECK_REMAINING(obj->length, truncated); - TRUNNEL_DYNARRAY_EXPAND(uint8_t, &obj->identifier, obj->length, {}); - obj->identifier.n_ = obj->length; - if (obj->length) - memcpy(obj->identifier.elts_, ptr, obj->length); - ptr += obj->length; remaining -= obj->length; + /* Parse u8 root_hash[32] */ + CHECK_REMAINING(32, truncated); + memcpy(obj->root_hash, ptr, 32); + remaining -= 32; ptr += 32; - /* Parse u8 signature[64] */ - CHECK_REMAINING(64, truncated); - memcpy(obj->signature, ptr, 64); - remaining -= 64; ptr += 64; + /* Parse u64 n_items */ + CHECK_REMAINING(8, truncated); + obj->n_items = trunnel_ntohll(trunnel_get_uint64(ptr)); + remaining -= 8; ptr += 8; - /* Parse struct ed25519_v1 namespace */ - result = ed25519_v1_parse(&obj->namespace, ptr, remaining); - if (result < 0) - goto relay_fail; - trunnel_assert((size_t)result <= remaining); - remaining -= result; ptr += result; + /* Parse struct sigident_ed25519 signatures[n_items] */ + TRUNNEL_DYNARRAY_EXPAND(sigident_ed25519_t *, &obj->signatures, obj->n_items, {}); + { + sigident_ed25519_t * elt; + unsigned idx; + for (idx = 0; idx < obj->n_items; ++idx) { + result = sigident_ed25519_parse(&elt, ptr, remaining); + if (result < 0) + goto relay_fail; + trunnel_assert((size_t)result <= remaining); + remaining -= result; ptr += result; + TRUNNEL_DYNARRAY_ADD(sigident_ed25519_t *, &obj->signatures, elt, {sigident_ed25519_free(elt);}); + } + } trunnel_assert(ptr + remaining == input + len_in); return len_in - remaining; @@ -2781,15 +3565,15 @@ signed_checksum32_ed25519_v1_parse_into(signed_checksum32_ed25519_v1_t *obj, con } ssize_t -signed_checksum32_ed25519_v1_parse(signed_checksum32_ed25519_v1_t **output, const uint8_t *input, const size_t len_in) +signed_tree_head_v1_parse(signed_tree_head_v1_t **output, const uint8_t *input, const size_t len_in) { ssize_t result; - *output = signed_checksum32_ed25519_v1_new(); + *output = signed_tree_head_v1_new(); if (NULL == *output) return -1; - result = signed_checksum32_ed25519_v1_parse_into(*output, input, len_in); + result = signed_tree_head_v1_parse_into(*output, input, len_in); if (result < 0) { - signed_checksum32_ed25519_v1_free(*output); + signed_tree_head_v1_free(*output); *output = NULL; } return result; diff --git a/trunnel/stfe.h b/trunnel/stfe.h index c92d1e4..26ffb09 100644 --- a/trunnel/stfe.h +++ b/trunnel/stfe.h @@ -8,22 +8,21 @@ #include #include "trunnel.h" +#define MAGIC_V1 6004501466958485041 #define T_GET_ENTRIES_V1 1 #define T_GET_PROOF_BY_HASH_V1 2 #define T_GET_CONSISTENCY_PROOF_V1 3 -#define T_INCLUSION_PROOF_V1 4 -#define T_CONSISTENCY_PROOF_V1 5 -#define T_SIGNED_TREE_HEAD_V1 6 -#define T_SIGNED_CHECKSUM32_ED25519_V1 7 -#define T_ED25519_V1 8 -#if !defined(TRUNNEL_OPAQUE) && !defined(TRUNNEL_OPAQUE_ED25519_V1) -struct ed25519_v1_st { - uint64_t format; - uint8_t pubkey[32]; +#define T_ENTRIES_V1 4 +#define T_INCLUSION_PROOF_V1 5 +#define T_CONSISTENCY_PROOF_V1 6 +#define T_SIGNED_TREE_HEAD_V1 7 +#if !defined(TRUNNEL_OPAQUE) && !defined(TRUNNEL_OPAQUE_HASH) +struct hash_st { + uint8_t hash[32]; uint8_t trunnel_error_code_; }; #endif -typedef struct ed25519_v1_st ed25519_v1_t; +typedef struct hash_st hash_t; #if !defined(TRUNNEL_OPAQUE) && !defined(TRUNNEL_OPAQUE_REQ_GET_CONSISTENCY_PROOF_V1) struct req_get_consistency_proof_v1_st { uint64_t old_size; @@ -48,44 +47,64 @@ struct req_get_proof_by_hash_v1_st { }; #endif typedef struct req_get_proof_by_hash_v1_st req_get_proof_by_hash_v1_t; -#if !defined(TRUNNEL_OPAQUE) && !defined(TRUNNEL_OPAQUE_SIGNED_TREE_HEAD_V1) -struct signed_tree_head_v1_st { - uint64_t format; - uint64_t timestamp; - uint64_t tree_size; - uint8_t root_hash[32]; +#if !defined(TRUNNEL_OPAQUE) && !defined(TRUNNEL_OPAQUE_SIGIDENT_ED25519) +struct sigident_ed25519_st { + uint8_t signature[64]; + uint8_t identifier[32]; + uint8_t trunnel_error_code_; +}; +#endif +typedef struct sigident_ed25519_st sigident_ed25519_t; +#if !defined(TRUNNEL_OPAQUE) && !defined(TRUNNEL_OPAQUE_SIGNED_CHECKSUM32_ED25519) +struct signed_checksum32_ed25519_st { + uint8_t checksum[32]; uint64_t length; - TRUNNEL_DYNARRAY_HEAD(, uint8_t) sigident; + TRUNNEL_DYNARRAY_HEAD(, uint8_t) identifier; + uint8_t signature[64]; + uint8_t namespace[32]; uint8_t trunnel_error_code_; }; #endif -typedef struct signed_tree_head_v1_st signed_tree_head_v1_t; +typedef struct signed_checksum32_ed25519_st signed_checksum32_ed25519_t; #if !defined(TRUNNEL_OPAQUE) && !defined(TRUNNEL_OPAQUE_CONSISTENCY_PROOF_V1) struct consistency_proof_v1_st { + uint64_t magic; uint64_t format; - struct ed25519_v1_st *identifier; + uint8_t identifier[32]; uint64_t old_size; uint64_t new_size; - uint64_t length; - TRUNNEL_DYNARRAY_HEAD(, uint8_t) hashes; + uint64_t n_items; + TRUNNEL_DYNARRAY_HEAD(, struct hash_st *) hashes; uint8_t trunnel_error_code_; }; #endif typedef struct consistency_proof_v1_st consistency_proof_v1_t; +#if !defined(TRUNNEL_OPAQUE) && !defined(TRUNNEL_OPAQUE_ENTRIES_V1) +struct entries_v1_st { + uint64_t magic; + uint64_t format; + uint64_t n_items; + TRUNNEL_DYNARRAY_HEAD(, struct signed_checksum32_ed25519_st *) checksums; + uint8_t trunnel_error_code_; +}; +#endif +typedef struct entries_v1_st entries_v1_t; #if !defined(TRUNNEL_OPAQUE) && !defined(TRUNNEL_OPAQUE_INCLUSION_PROOF_V1) struct inclusion_proof_v1_st { + uint64_t magic; uint64_t format; - struct ed25519_v1_st *identifier; + uint8_t identifier[32]; uint64_t tree_size; uint64_t leaf_index; - uint64_t length; - TRUNNEL_DYNARRAY_HEAD(, uint8_t) hashes; + uint64_t n_items; + TRUNNEL_DYNARRAY_HEAD(, struct hash_st *) hashes; uint8_t trunnel_error_code_; }; #endif typedef struct inclusion_proof_v1_st inclusion_proof_v1_t; #if !defined(TRUNNEL_OPAQUE) && !defined(TRUNNEL_OPAQUE_REQUEST_V1) struct request_v1_st { + uint64_t magic; uint64_t format; struct req_get_entries_v1_st *request_get_entries; struct req_get_proof_by_hash_v1_st *request_get_proof_by_hash; @@ -94,82 +113,74 @@ struct request_v1_st { }; #endif typedef struct request_v1_st request_v1_t; -#if !defined(TRUNNEL_OPAQUE) && !defined(TRUNNEL_OPAQUE_SIGNED_CHECKSUM32_ED25519_V1) -struct signed_checksum32_ed25519_v1_st { +#if !defined(TRUNNEL_OPAQUE) && !defined(TRUNNEL_OPAQUE_SIGNED_TREE_HEAD_V1) +struct signed_tree_head_v1_st { + uint64_t magic; uint64_t format; - uint8_t checksum[32]; - uint64_t length; - TRUNNEL_DYNARRAY_HEAD(, uint8_t) identifier; - uint8_t signature[64]; - struct ed25519_v1_st *namespace; + uint64_t timestamp; + uint64_t tree_size; + uint8_t root_hash[32]; + uint64_t n_items; + TRUNNEL_DYNARRAY_HEAD(, struct sigident_ed25519_st *) signatures; uint8_t trunnel_error_code_; }; #endif -typedef struct signed_checksum32_ed25519_v1_st signed_checksum32_ed25519_v1_t; -/** Return a newly allocated ed25519_v1 with all elements set to zero. +typedef struct signed_tree_head_v1_st signed_tree_head_v1_t; +/** Return a newly allocated hash with all elements set to zero. */ -ed25519_v1_t *ed25519_v1_new(void); -/** Release all storage held by the ed25519_v1 in 'victim'. (Do - * nothing if 'victim' is NULL.) +hash_t *hash_new(void); +/** Release all storage held by the hash in 'victim'. (Do nothing if + * 'victim' is NULL.) */ -void ed25519_v1_free(ed25519_v1_t *victim); -/** Try to parse a ed25519_v1 from the buffer in 'input', using up to +void hash_free(hash_t *victim); +/** Try to parse a hash from the buffer in 'input', using up to * 'len_in' bytes from the input buffer. On success, return the number - * of bytes consumed and set *output to the newly allocated - * ed25519_v1_t. On failure, return -2 if the input appears truncated, - * and -1 if the input is otherwise invalid. - */ -ssize_t ed25519_v1_parse(ed25519_v1_t **output, const uint8_t *input, const size_t len_in); -/** Return the number of bytes we expect to need to encode the - * ed25519_v1 in 'obj'. On failure, return a negative value. Note that - * this value may be an overestimate, and can even be an underestimate - * for certain unencodeable objects. - */ -ssize_t ed25519_v1_encoded_len(const ed25519_v1_t *obj); -/** Try to encode the ed25519_v1 from 'input' into the buffer at - * 'output', using up to 'avail' bytes of the output buffer. On - * success, return the number of bytes used. On failure, return -2 if - * the buffer was not long enough, and -1 if the input was invalid. - */ -ssize_t ed25519_v1_encode(uint8_t *output, size_t avail, const ed25519_v1_t *input); -/** Check whether the internal state of the ed25519_v1 in 'obj' is + * of bytes consumed and set *output to the newly allocated hash_t. On + * failure, return -2 if the input appears truncated, and -1 if the + * input is otherwise invalid. + */ +ssize_t hash_parse(hash_t **output, const uint8_t *input, const size_t len_in); +/** Return the number of bytes we expect to need to encode the hash in + * 'obj'. On failure, return a negative value. Note that this value + * may be an overestimate, and can even be an underestimate for + * certain unencodeable objects. + */ +ssize_t hash_encoded_len(const hash_t *obj); +/** Try to encode the hash from 'input' into the buffer at 'output', + * using up to 'avail' bytes of the output buffer. On success, return + * the number of bytes used. On failure, return -2 if the buffer was + * not long enough, and -1 if the input was invalid. + */ +ssize_t hash_encode(uint8_t *output, size_t avail, const hash_t *input); +/** Check whether the internal state of the hash in 'obj' is * consistent. Return NULL if it is, and a short message if it is not. */ -const char *ed25519_v1_check(const ed25519_v1_t *obj); +const char *hash_check(const hash_t *obj); /** Clear any errors that were set on the object 'obj' by its setter * functions. Return true iff errors were cleared. */ -int ed25519_v1_clear_errors(ed25519_v1_t *obj); -/** Return the value of the format field of the ed25519_v1_t in 'inp' +int hash_clear_errors(hash_t *obj); +/** Return the (constant) length of the array holding the hash field + * of the hash_t in 'inp'. */ -uint64_t ed25519_v1_get_format(const ed25519_v1_t *inp); -/** Set the value of the format field of the ed25519_v1_t in 'inp' to - * 'val'. Return 0 on success; return -1 and set the error code on - * 'inp' on failure. +size_t hash_getlen_hash(const hash_t *inp); +/** Return the element at position 'idx' of the fixed array field hash + * of the hash_t in 'inp'. */ -int ed25519_v1_set_format(ed25519_v1_t *inp, uint64_t val); -/** Return the (constant) length of the array holding the pubkey field - * of the ed25519_v1_t in 'inp'. +uint8_t hash_get_hash(hash_t *inp, size_t idx); +/** As hash_get_hash, but take and return a const pointer */ -size_t ed25519_v1_getlen_pubkey(const ed25519_v1_t *inp); -/** Return the element at position 'idx' of the fixed array field - * pubkey of the ed25519_v1_t in 'inp'. +uint8_t hash_getconst_hash(const hash_t *inp, size_t idx); +/** Change the element at position 'idx' of the fixed array field hash + * of the hash_t in 'inp', so that it will hold the value 'elt'. */ -uint8_t ed25519_v1_get_pubkey(ed25519_v1_t *inp, size_t idx); -/** As ed25519_v1_get_pubkey, but take and return a const pointer +int hash_set_hash(hash_t *inp, size_t idx, uint8_t elt); +/** Return a pointer to the 32-element array field hash of 'inp'. */ -uint8_t ed25519_v1_getconst_pubkey(const ed25519_v1_t *inp, size_t idx); -/** Change the element at position 'idx' of the fixed array field - * pubkey of the ed25519_v1_t in 'inp', so that it will hold the value - * 'elt'. - */ -int ed25519_v1_set_pubkey(ed25519_v1_t *inp, size_t idx, uint8_t elt); -/** Return a pointer to the 32-element array field pubkey of 'inp'. - */ -uint8_t * ed25519_v1_getarray_pubkey(ed25519_v1_t *inp); -/** As ed25519_v1_get_pubkey, but take and return a const pointer +uint8_t * hash_getarray_hash(hash_t *inp); +/** As hash_get_hash, but take and return a const pointer */ -const uint8_t * ed25519_v1_getconstarray_pubkey(const ed25519_v1_t *inp); +const uint8_t * hash_getconstarray_hash(const hash_t *inp); /** Return a newly allocated req_get_consistency_proof_v1 with all * elements set to zero. */ @@ -350,136 +361,243 @@ uint8_t * req_get_proof_by_hash_v1_getarray_leaf_hash(req_get_proof_by_hash_v1_t * const pointer */ const uint8_t * req_get_proof_by_hash_v1_getconstarray_leaf_hash(const req_get_proof_by_hash_v1_t *inp); -/** Return a newly allocated signed_tree_head_v1 with all elements set - * to zero. +/** Return a newly allocated sigident_ed25519 with all elements set to + * zero. */ -signed_tree_head_v1_t *signed_tree_head_v1_new(void); -/** Release all storage held by the signed_tree_head_v1 in 'victim'. - * (Do nothing if 'victim' is NULL.) +sigident_ed25519_t *sigident_ed25519_new(void); +/** Release all storage held by the sigident_ed25519 in 'victim'. (Do + * nothing if 'victim' is NULL.) */ -void signed_tree_head_v1_free(signed_tree_head_v1_t *victim); -/** Try to parse a signed_tree_head_v1 from the buffer in 'input', - * using up to 'len_in' bytes from the input buffer. On success, - * return the number of bytes consumed and set *output to the newly - * allocated signed_tree_head_v1_t. On failure, return -2 if the input - * appears truncated, and -1 if the input is otherwise invalid. +void sigident_ed25519_free(sigident_ed25519_t *victim); +/** Try to parse a sigident_ed25519 from the buffer in 'input', using + * up to 'len_in' bytes from the input buffer. On success, return the + * number of bytes consumed and set *output to the newly allocated + * sigident_ed25519_t. On failure, return -2 if the input appears + * truncated, and -1 if the input is otherwise invalid. */ -ssize_t signed_tree_head_v1_parse(signed_tree_head_v1_t **output, const uint8_t *input, const size_t len_in); +ssize_t sigident_ed25519_parse(sigident_ed25519_t **output, const uint8_t *input, const size_t len_in); /** Return the number of bytes we expect to need to encode the - * signed_tree_head_v1 in 'obj'. On failure, return a negative value. + * sigident_ed25519 in 'obj'. On failure, return a negative value. * Note that this value may be an overestimate, and can even be an * underestimate for certain unencodeable objects. */ -ssize_t signed_tree_head_v1_encoded_len(const signed_tree_head_v1_t *obj); -/** Try to encode the signed_tree_head_v1 from 'input' into the buffer - * at 'output', using up to 'avail' bytes of the output buffer. On +ssize_t sigident_ed25519_encoded_len(const sigident_ed25519_t *obj); +/** Try to encode the sigident_ed25519 from 'input' into the buffer at + * 'output', using up to 'avail' bytes of the output buffer. On * success, return the number of bytes used. On failure, return -2 if * the buffer was not long enough, and -1 if the input was invalid. */ -ssize_t signed_tree_head_v1_encode(uint8_t *output, size_t avail, const signed_tree_head_v1_t *input); -/** Check whether the internal state of the signed_tree_head_v1 in - * 'obj' is consistent. Return NULL if it is, and a short message if - * it is not. +ssize_t sigident_ed25519_encode(uint8_t *output, size_t avail, const sigident_ed25519_t *input); +/** Check whether the internal state of the sigident_ed25519 in 'obj' + * is consistent. Return NULL if it is, and a short message if it is + * not. */ -const char *signed_tree_head_v1_check(const signed_tree_head_v1_t *obj); +const char *sigident_ed25519_check(const sigident_ed25519_t *obj); /** Clear any errors that were set on the object 'obj' by its setter * functions. Return true iff errors were cleared. */ -int signed_tree_head_v1_clear_errors(signed_tree_head_v1_t *obj); -/** Return the value of the format field of the signed_tree_head_v1_t - * in 'inp' +int sigident_ed25519_clear_errors(sigident_ed25519_t *obj); +/** Return the (constant) length of the array holding the signature + * field of the sigident_ed25519_t in 'inp'. */ -uint64_t signed_tree_head_v1_get_format(const signed_tree_head_v1_t *inp); -/** Set the value of the format field of the signed_tree_head_v1_t in - * 'inp' to 'val'. Return 0 on success; return -1 and set the error - * code on 'inp' on failure. +size_t sigident_ed25519_getlen_signature(const sigident_ed25519_t *inp); +/** Return the element at position 'idx' of the fixed array field + * signature of the sigident_ed25519_t in 'inp'. */ -int signed_tree_head_v1_set_format(signed_tree_head_v1_t *inp, uint64_t val); -/** Return the value of the timestamp field of the - * signed_tree_head_v1_t in 'inp' +uint8_t sigident_ed25519_get_signature(sigident_ed25519_t *inp, size_t idx); +/** As sigident_ed25519_get_signature, but take and return a const + * pointer */ -uint64_t signed_tree_head_v1_get_timestamp(const signed_tree_head_v1_t *inp); -/** Set the value of the timestamp field of the signed_tree_head_v1_t - * in 'inp' to 'val'. Return 0 on success; return -1 and set the error - * code on 'inp' on failure. +uint8_t sigident_ed25519_getconst_signature(const sigident_ed25519_t *inp, size_t idx); +/** Change the element at position 'idx' of the fixed array field + * signature of the sigident_ed25519_t in 'inp', so that it will hold + * the value 'elt'. */ -int signed_tree_head_v1_set_timestamp(signed_tree_head_v1_t *inp, uint64_t val); -/** Return the value of the tree_size field of the - * signed_tree_head_v1_t in 'inp' +int sigident_ed25519_set_signature(sigident_ed25519_t *inp, size_t idx, uint8_t elt); +/** Return a pointer to the 64-element array field signature of 'inp'. */ -uint64_t signed_tree_head_v1_get_tree_size(const signed_tree_head_v1_t *inp); -/** Set the value of the tree_size field of the signed_tree_head_v1_t - * in 'inp' to 'val'. Return 0 on success; return -1 and set the error - * code on 'inp' on failure. +uint8_t * sigident_ed25519_getarray_signature(sigident_ed25519_t *inp); +/** As sigident_ed25519_get_signature, but take and return a const + * pointer */ -int signed_tree_head_v1_set_tree_size(signed_tree_head_v1_t *inp, uint64_t val); -/** Return the (constant) length of the array holding the root_hash - * field of the signed_tree_head_v1_t in 'inp'. +const uint8_t * sigident_ed25519_getconstarray_signature(const sigident_ed25519_t *inp); +/** Return the (constant) length of the array holding the identifier + * field of the sigident_ed25519_t in 'inp'. */ -size_t signed_tree_head_v1_getlen_root_hash(const signed_tree_head_v1_t *inp); +size_t sigident_ed25519_getlen_identifier(const sigident_ed25519_t *inp); /** Return the element at position 'idx' of the fixed array field - * root_hash of the signed_tree_head_v1_t in 'inp'. + * identifier of the sigident_ed25519_t in 'inp'. */ -uint8_t signed_tree_head_v1_get_root_hash(signed_tree_head_v1_t *inp, size_t idx); -/** As signed_tree_head_v1_get_root_hash, but take and return a const +uint8_t sigident_ed25519_get_identifier(sigident_ed25519_t *inp, size_t idx); +/** As sigident_ed25519_get_identifier, but take and return a const * pointer */ -uint8_t signed_tree_head_v1_getconst_root_hash(const signed_tree_head_v1_t *inp, size_t idx); +uint8_t sigident_ed25519_getconst_identifier(const sigident_ed25519_t *inp, size_t idx); /** Change the element at position 'idx' of the fixed array field - * root_hash of the signed_tree_head_v1_t in 'inp', so that it will - * hold the value 'elt'. + * identifier of the sigident_ed25519_t in 'inp', so that it will hold + * the value 'elt'. */ -int signed_tree_head_v1_set_root_hash(signed_tree_head_v1_t *inp, size_t idx, uint8_t elt); -/** Return a pointer to the 32-element array field root_hash of 'inp'. +int sigident_ed25519_set_identifier(sigident_ed25519_t *inp, size_t idx, uint8_t elt); +/** Return a pointer to the 32-element array field identifier of + * 'inp'. */ -uint8_t * signed_tree_head_v1_getarray_root_hash(signed_tree_head_v1_t *inp); -/** As signed_tree_head_v1_get_root_hash, but take and return a const +uint8_t * sigident_ed25519_getarray_identifier(sigident_ed25519_t *inp); +/** As sigident_ed25519_get_identifier, but take and return a const * pointer */ -const uint8_t * signed_tree_head_v1_getconstarray_root_hash(const signed_tree_head_v1_t *inp); -/** Return the value of the length field of the signed_tree_head_v1_t - * in 'inp' +const uint8_t * sigident_ed25519_getconstarray_identifier(const sigident_ed25519_t *inp); +/** Return a newly allocated signed_checksum32_ed25519 with all + * elements set to zero. */ -uint64_t signed_tree_head_v1_get_length(const signed_tree_head_v1_t *inp); -/** Set the value of the length field of the signed_tree_head_v1_t in - * 'inp' to 'val'. Return 0 on success; return -1 and set the error - * code on 'inp' on failure. +signed_checksum32_ed25519_t *signed_checksum32_ed25519_new(void); +/** Release all storage held by the signed_checksum32_ed25519 in + * 'victim'. (Do nothing if 'victim' is NULL.) */ -int signed_tree_head_v1_set_length(signed_tree_head_v1_t *inp, uint64_t val); -/** Return the length of the dynamic array holding the sigident field - * of the signed_tree_head_v1_t in 'inp'. +void signed_checksum32_ed25519_free(signed_checksum32_ed25519_t *victim); +/** Try to parse a signed_checksum32_ed25519 from the buffer in + * 'input', using up to 'len_in' bytes from the input buffer. On + * success, return the number of bytes consumed and set *output to the + * newly allocated signed_checksum32_ed25519_t. On failure, return -2 + * if the input appears truncated, and -1 if the input is otherwise + * invalid. + */ +ssize_t signed_checksum32_ed25519_parse(signed_checksum32_ed25519_t **output, const uint8_t *input, const size_t len_in); +/** Return the number of bytes we expect to need to encode the + * signed_checksum32_ed25519 in 'obj'. On failure, return a negative + * value. Note that this value may be an overestimate, and can even be + * an underestimate for certain unencodeable objects. + */ +ssize_t signed_checksum32_ed25519_encoded_len(const signed_checksum32_ed25519_t *obj); +/** Try to encode the signed_checksum32_ed25519 from 'input' into the + * buffer at 'output', using up to 'avail' bytes of the output buffer. + * On success, return the number of bytes used. On failure, return -2 + * if the buffer was not long enough, and -1 if the input was invalid. + */ +ssize_t signed_checksum32_ed25519_encode(uint8_t *output, size_t avail, const signed_checksum32_ed25519_t *input); +/** Check whether the internal state of the signed_checksum32_ed25519 + * in 'obj' is consistent. Return NULL if it is, and a short message + * if it is not. + */ +const char *signed_checksum32_ed25519_check(const signed_checksum32_ed25519_t *obj); +/** Clear any errors that were set on the object 'obj' by its setter + * functions. Return true iff errors were cleared. + */ +int signed_checksum32_ed25519_clear_errors(signed_checksum32_ed25519_t *obj); +/** Return the (constant) length of the array holding the checksum + * field of the signed_checksum32_ed25519_t in 'inp'. + */ +size_t signed_checksum32_ed25519_getlen_checksum(const signed_checksum32_ed25519_t *inp); +/** Return the element at position 'idx' of the fixed array field + * checksum of the signed_checksum32_ed25519_t in 'inp'. */ -size_t signed_tree_head_v1_getlen_sigident(const signed_tree_head_v1_t *inp); +uint8_t signed_checksum32_ed25519_get_checksum(signed_checksum32_ed25519_t *inp, size_t idx); +/** As signed_checksum32_ed25519_get_checksum, but take and return a + * const pointer + */ +uint8_t signed_checksum32_ed25519_getconst_checksum(const signed_checksum32_ed25519_t *inp, size_t idx); +/** Change the element at position 'idx' of the fixed array field + * checksum of the signed_checksum32_ed25519_t in 'inp', so that it + * will hold the value 'elt'. + */ +int signed_checksum32_ed25519_set_checksum(signed_checksum32_ed25519_t *inp, size_t idx, uint8_t elt); +/** Return a pointer to the 32-element array field checksum of 'inp'. + */ +uint8_t * signed_checksum32_ed25519_getarray_checksum(signed_checksum32_ed25519_t *inp); +/** As signed_checksum32_ed25519_get_checksum, but take and return a + * const pointer + */ +const uint8_t * signed_checksum32_ed25519_getconstarray_checksum(const signed_checksum32_ed25519_t *inp); +/** Return the value of the length field of the + * signed_checksum32_ed25519_t in 'inp' + */ +uint64_t signed_checksum32_ed25519_get_length(const signed_checksum32_ed25519_t *inp); +/** Set the value of the length field of the + * signed_checksum32_ed25519_t in 'inp' to 'val'. Return 0 on success; + * return -1 and set the error code on 'inp' on failure. + */ +int signed_checksum32_ed25519_set_length(signed_checksum32_ed25519_t *inp, uint64_t val); +/** Return the length of the dynamic array holding the identifier + * field of the signed_checksum32_ed25519_t in 'inp'. + */ +size_t signed_checksum32_ed25519_getlen_identifier(const signed_checksum32_ed25519_t *inp); /** Return the element at position 'idx' of the dynamic array field - * sigident of the signed_tree_head_v1_t in 'inp'. + * identifier of the signed_checksum32_ed25519_t in 'inp'. */ -uint8_t signed_tree_head_v1_get_sigident(signed_tree_head_v1_t *inp, size_t idx); -/** As signed_tree_head_v1_get_sigident, but take and return a const - * pointer +uint8_t signed_checksum32_ed25519_get_identifier(signed_checksum32_ed25519_t *inp, size_t idx); +/** As signed_checksum32_ed25519_get_identifier, but take and return a + * const pointer */ -uint8_t signed_tree_head_v1_getconst_sigident(const signed_tree_head_v1_t *inp, size_t idx); +uint8_t signed_checksum32_ed25519_getconst_identifier(const signed_checksum32_ed25519_t *inp, size_t idx); /** Change the element at position 'idx' of the dynamic array field - * sigident of the signed_tree_head_v1_t in 'inp', so that it will - * hold the value 'elt'. + * identifier of the signed_checksum32_ed25519_t in 'inp', so that it + * will hold the value 'elt'. */ -int signed_tree_head_v1_set_sigident(signed_tree_head_v1_t *inp, size_t idx, uint8_t elt); -/** Append a new element 'elt' to the dynamic array field sigident of - * the signed_tree_head_v1_t in 'inp'. +int signed_checksum32_ed25519_set_identifier(signed_checksum32_ed25519_t *inp, size_t idx, uint8_t elt); +/** Append a new element 'elt' to the dynamic array field identifier + * of the signed_checksum32_ed25519_t in 'inp'. */ -int signed_tree_head_v1_add_sigident(signed_tree_head_v1_t *inp, uint8_t elt); -/** Return a pointer to the variable-length array field sigident of +int signed_checksum32_ed25519_add_identifier(signed_checksum32_ed25519_t *inp, uint8_t elt); +/** Return a pointer to the variable-length array field identifier of * 'inp'. */ -uint8_t * signed_tree_head_v1_getarray_sigident(signed_tree_head_v1_t *inp); -/** As signed_tree_head_v1_get_sigident, but take and return a const - * pointer +uint8_t * signed_checksum32_ed25519_getarray_identifier(signed_checksum32_ed25519_t *inp); +/** As signed_checksum32_ed25519_get_identifier, but take and return a + * const pointer */ -const uint8_t * signed_tree_head_v1_getconstarray_sigident(const signed_tree_head_v1_t *inp); -/** Change the length of the variable-length array field sigident of +const uint8_t * signed_checksum32_ed25519_getconstarray_identifier(const signed_checksum32_ed25519_t *inp); +/** Change the length of the variable-length array field identifier of * 'inp' to 'newlen'.Fill extra elements with 0. Return 0 on success; * return -1 and set the error code on 'inp' on failure. */ -int signed_tree_head_v1_setlen_sigident(signed_tree_head_v1_t *inp, size_t newlen); +int signed_checksum32_ed25519_setlen_identifier(signed_checksum32_ed25519_t *inp, size_t newlen); +/** Return the (constant) length of the array holding the signature + * field of the signed_checksum32_ed25519_t in 'inp'. + */ +size_t signed_checksum32_ed25519_getlen_signature(const signed_checksum32_ed25519_t *inp); +/** Return the element at position 'idx' of the fixed array field + * signature of the signed_checksum32_ed25519_t in 'inp'. + */ +uint8_t signed_checksum32_ed25519_get_signature(signed_checksum32_ed25519_t *inp, size_t idx); +/** As signed_checksum32_ed25519_get_signature, but take and return a + * const pointer + */ +uint8_t signed_checksum32_ed25519_getconst_signature(const signed_checksum32_ed25519_t *inp, size_t idx); +/** Change the element at position 'idx' of the fixed array field + * signature of the signed_checksum32_ed25519_t in 'inp', so that it + * will hold the value 'elt'. + */ +int signed_checksum32_ed25519_set_signature(signed_checksum32_ed25519_t *inp, size_t idx, uint8_t elt); +/** Return a pointer to the 64-element array field signature of 'inp'. + */ +uint8_t * signed_checksum32_ed25519_getarray_signature(signed_checksum32_ed25519_t *inp); +/** As signed_checksum32_ed25519_get_signature, but take and return a + * const pointer + */ +const uint8_t * signed_checksum32_ed25519_getconstarray_signature(const signed_checksum32_ed25519_t *inp); +/** Return the (constant) length of the array holding the namespace + * field of the signed_checksum32_ed25519_t in 'inp'. + */ +size_t signed_checksum32_ed25519_getlen_namespace(const signed_checksum32_ed25519_t *inp); +/** Return the element at position 'idx' of the fixed array field + * namespace of the signed_checksum32_ed25519_t in 'inp'. + */ +uint8_t signed_checksum32_ed25519_get_namespace(signed_checksum32_ed25519_t *inp, size_t idx); +/** As signed_checksum32_ed25519_get_namespace, but take and return a + * const pointer + */ +uint8_t signed_checksum32_ed25519_getconst_namespace(const signed_checksum32_ed25519_t *inp, size_t idx); +/** Change the element at position 'idx' of the fixed array field + * namespace of the signed_checksum32_ed25519_t in 'inp', so that it + * will hold the value 'elt'. + */ +int signed_checksum32_ed25519_set_namespace(signed_checksum32_ed25519_t *inp, size_t idx, uint8_t elt); +/** Return a pointer to the 32-element array field namespace of 'inp'. + */ +uint8_t * signed_checksum32_ed25519_getarray_namespace(signed_checksum32_ed25519_t *inp); +/** As signed_checksum32_ed25519_get_namespace, but take and return a + * const pointer + */ +const uint8_t * signed_checksum32_ed25519_getconstarray_namespace(const signed_checksum32_ed25519_t *inp); /** Return a newly allocated consistency_proof_v1 with all elements * set to zero. */ @@ -516,6 +634,15 @@ const char *consistency_proof_v1_check(const consistency_proof_v1_t *obj); * functions. Return true iff errors were cleared. */ int consistency_proof_v1_clear_errors(consistency_proof_v1_t *obj); +/** Return the value of the magic field of the consistency_proof_v1_t + * in 'inp' + */ +uint64_t consistency_proof_v1_get_magic(const consistency_proof_v1_t *inp); +/** Set the value of the magic field of the consistency_proof_v1_t in + * 'inp' to 'val'. Return 0 on success; return -1 and set the error + * code on 'inp' on failure. + */ +int consistency_proof_v1_set_magic(consistency_proof_v1_t *inp, uint64_t val); /** Return the value of the format field of the consistency_proof_v1_t * in 'inp' */ @@ -525,24 +652,31 @@ uint64_t consistency_proof_v1_get_format(const consistency_proof_v1_t *inp); * code on 'inp' on failure. */ int consistency_proof_v1_set_format(consistency_proof_v1_t *inp, uint64_t val); -/** Return the value of the identifier field of the - * consistency_proof_v1_t in 'inp' +/** Return the (constant) length of the array holding the identifier + * field of the consistency_proof_v1_t in 'inp'. */ -struct ed25519_v1_st * consistency_proof_v1_get_identifier(consistency_proof_v1_t *inp); +size_t consistency_proof_v1_getlen_identifier(const consistency_proof_v1_t *inp); +/** Return the element at position 'idx' of the fixed array field + * identifier of the consistency_proof_v1_t in 'inp'. + */ +uint8_t consistency_proof_v1_get_identifier(consistency_proof_v1_t *inp, size_t idx); /** As consistency_proof_v1_get_identifier, but take and return a * const pointer */ -const struct ed25519_v1_st * consistency_proof_v1_getconst_identifier(const consistency_proof_v1_t *inp); -/** Set the value of the identifier field of the - * consistency_proof_v1_t in 'inp' to 'val'. Free the old value if - * any. Steals the referenceto 'val'.Return 0 on success; return -1 - * and set the error code on 'inp' on failure. +uint8_t consistency_proof_v1_getconst_identifier(const consistency_proof_v1_t *inp, size_t idx); +/** Change the element at position 'idx' of the fixed array field + * identifier of the consistency_proof_v1_t in 'inp', so that it will + * hold the value 'elt'. */ -int consistency_proof_v1_set_identifier(consistency_proof_v1_t *inp, struct ed25519_v1_st *val); -/** As consistency_proof_v1_set_identifier, but does not free the - * previous value. +int consistency_proof_v1_set_identifier(consistency_proof_v1_t *inp, size_t idx, uint8_t elt); +/** Return a pointer to the 32-element array field identifier of + * 'inp'. */ -int consistency_proof_v1_set0_identifier(consistency_proof_v1_t *inp, struct ed25519_v1_st *val); +uint8_t * consistency_proof_v1_getarray_identifier(consistency_proof_v1_t *inp); +/** As consistency_proof_v1_get_identifier, but take and return a + * const pointer + */ +const uint8_t * consistency_proof_v1_getconstarray_identifier(const consistency_proof_v1_t *inp); /** Return the value of the old_size field of the * consistency_proof_v1_t in 'inp' */ @@ -561,15 +695,15 @@ uint64_t consistency_proof_v1_get_new_size(const consistency_proof_v1_t *inp); * code on 'inp' on failure. */ int consistency_proof_v1_set_new_size(consistency_proof_v1_t *inp, uint64_t val); -/** Return the value of the length field of the consistency_proof_v1_t - * in 'inp' +/** Return the value of the n_items field of the + * consistency_proof_v1_t in 'inp' */ -uint64_t consistency_proof_v1_get_length(const consistency_proof_v1_t *inp); -/** Set the value of the length field of the consistency_proof_v1_t in - * 'inp' to 'val'. Return 0 on success; return -1 and set the error +uint64_t consistency_proof_v1_get_n_items(const consistency_proof_v1_t *inp); +/** Set the value of the n_items field of the consistency_proof_v1_t + * in 'inp' to 'val'. Return 0 on success; return -1 and set the error * code on 'inp' on failure. */ -int consistency_proof_v1_set_length(consistency_proof_v1_t *inp, uint64_t val); +int consistency_proof_v1_set_n_items(consistency_proof_v1_t *inp, uint64_t val); /** Return the length of the dynamic array holding the hashes field of * the consistency_proof_v1_t in 'inp'. */ @@ -577,33 +711,132 @@ size_t consistency_proof_v1_getlen_hashes(const consistency_proof_v1_t *inp); /** Return the element at position 'idx' of the dynamic array field * hashes of the consistency_proof_v1_t in 'inp'. */ -uint8_t consistency_proof_v1_get_hashes(consistency_proof_v1_t *inp, size_t idx); +struct hash_st * consistency_proof_v1_get_hashes(consistency_proof_v1_t *inp, size_t idx); /** As consistency_proof_v1_get_hashes, but take and return a const * pointer */ -uint8_t consistency_proof_v1_getconst_hashes(const consistency_proof_v1_t *inp, size_t idx); + const struct hash_st * consistency_proof_v1_getconst_hashes(const consistency_proof_v1_t *inp, size_t idx); /** Change the element at position 'idx' of the dynamic array field * hashes of the consistency_proof_v1_t in 'inp', so that it will hold - * the value 'elt'. + * the value 'elt'. Free the previous value, if any. + */ +int consistency_proof_v1_set_hashes(consistency_proof_v1_t *inp, size_t idx, struct hash_st * elt); +/** As consistency_proof_v1_set_hashes, but does not free the previous + * value. */ -int consistency_proof_v1_set_hashes(consistency_proof_v1_t *inp, size_t idx, uint8_t elt); +int consistency_proof_v1_set0_hashes(consistency_proof_v1_t *inp, size_t idx, struct hash_st * elt); /** Append a new element 'elt' to the dynamic array field hashes of * the consistency_proof_v1_t in 'inp'. */ -int consistency_proof_v1_add_hashes(consistency_proof_v1_t *inp, uint8_t elt); +int consistency_proof_v1_add_hashes(consistency_proof_v1_t *inp, struct hash_st * elt); /** Return a pointer to the variable-length array field hashes of * 'inp'. */ -uint8_t * consistency_proof_v1_getarray_hashes(consistency_proof_v1_t *inp); +struct hash_st * * consistency_proof_v1_getarray_hashes(consistency_proof_v1_t *inp); /** As consistency_proof_v1_get_hashes, but take and return a const * pointer */ -const uint8_t * consistency_proof_v1_getconstarray_hashes(const consistency_proof_v1_t *inp); +const struct hash_st * const * consistency_proof_v1_getconstarray_hashes(const consistency_proof_v1_t *inp); /** Change the length of the variable-length array field hashes of - * 'inp' to 'newlen'.Fill extra elements with 0. Return 0 on success; - * return -1 and set the error code on 'inp' on failure. + * 'inp' to 'newlen'.Fill extra elements with NULL; free removed + * elements. Return 0 on success; return -1 and set the error code on + * 'inp' on failure. */ int consistency_proof_v1_setlen_hashes(consistency_proof_v1_t *inp, size_t newlen); +/** Return a newly allocated entries_v1 with all elements set to zero. + */ +entries_v1_t *entries_v1_new(void); +/** Release all storage held by the entries_v1 in 'victim'. (Do + * nothing if 'victim' is NULL.) + */ +void entries_v1_free(entries_v1_t *victim); +/** Try to parse a entries_v1 from the buffer in 'input', using up to + * 'len_in' bytes from the input buffer. On success, return the number + * of bytes consumed and set *output to the newly allocated + * entries_v1_t. On failure, return -2 if the input appears truncated, + * and -1 if the input is otherwise invalid. + */ +ssize_t entries_v1_parse(entries_v1_t **output, const uint8_t *input, const size_t len_in); +/** Return the number of bytes we expect to need to encode the + * entries_v1 in 'obj'. On failure, return a negative value. Note that + * this value may be an overestimate, and can even be an underestimate + * for certain unencodeable objects. + */ +ssize_t entries_v1_encoded_len(const entries_v1_t *obj); +/** Try to encode the entries_v1 from 'input' into the buffer at + * 'output', using up to 'avail' bytes of the output buffer. On + * success, return the number of bytes used. On failure, return -2 if + * the buffer was not long enough, and -1 if the input was invalid. + */ +ssize_t entries_v1_encode(uint8_t *output, size_t avail, const entries_v1_t *input); +/** Check whether the internal state of the entries_v1 in 'obj' is + * consistent. Return NULL if it is, and a short message if it is not. + */ +const char *entries_v1_check(const entries_v1_t *obj); +/** Clear any errors that were set on the object 'obj' by its setter + * functions. Return true iff errors were cleared. + */ +int entries_v1_clear_errors(entries_v1_t *obj); +/** Return the value of the magic field of the entries_v1_t in 'inp' + */ +uint64_t entries_v1_get_magic(const entries_v1_t *inp); +/** Set the value of the magic field of the entries_v1_t in 'inp' to + * 'val'. Return 0 on success; return -1 and set the error code on + * 'inp' on failure. + */ +int entries_v1_set_magic(entries_v1_t *inp, uint64_t val); +/** Return the value of the format field of the entries_v1_t in 'inp' + */ +uint64_t entries_v1_get_format(const entries_v1_t *inp); +/** Set the value of the format field of the entries_v1_t in 'inp' to + * 'val'. Return 0 on success; return -1 and set the error code on + * 'inp' on failure. + */ +int entries_v1_set_format(entries_v1_t *inp, uint64_t val); +/** Return the value of the n_items field of the entries_v1_t in 'inp' + */ +uint64_t entries_v1_get_n_items(const entries_v1_t *inp); +/** Set the value of the n_items field of the entries_v1_t in 'inp' to + * 'val'. Return 0 on success; return -1 and set the error code on + * 'inp' on failure. + */ +int entries_v1_set_n_items(entries_v1_t *inp, uint64_t val); +/** Return the length of the dynamic array holding the checksums field + * of the entries_v1_t in 'inp'. + */ +size_t entries_v1_getlen_checksums(const entries_v1_t *inp); +/** Return the element at position 'idx' of the dynamic array field + * checksums of the entries_v1_t in 'inp'. + */ +struct signed_checksum32_ed25519_st * entries_v1_get_checksums(entries_v1_t *inp, size_t idx); +/** As entries_v1_get_checksums, but take and return a const pointer + */ + const struct signed_checksum32_ed25519_st * entries_v1_getconst_checksums(const entries_v1_t *inp, size_t idx); +/** Change the element at position 'idx' of the dynamic array field + * checksums of the entries_v1_t in 'inp', so that it will hold the + * value 'elt'. Free the previous value, if any. + */ +int entries_v1_set_checksums(entries_v1_t *inp, size_t idx, struct signed_checksum32_ed25519_st * elt); +/** As entries_v1_set_checksums, but does not free the previous value. + */ +int entries_v1_set0_checksums(entries_v1_t *inp, size_t idx, struct signed_checksum32_ed25519_st * elt); +/** Append a new element 'elt' to the dynamic array field checksums of + * the entries_v1_t in 'inp'. + */ +int entries_v1_add_checksums(entries_v1_t *inp, struct signed_checksum32_ed25519_st * elt); +/** Return a pointer to the variable-length array field checksums of + * 'inp'. + */ +struct signed_checksum32_ed25519_st * * entries_v1_getarray_checksums(entries_v1_t *inp); +/** As entries_v1_get_checksums, but take and return a const pointer + */ +const struct signed_checksum32_ed25519_st * const * entries_v1_getconstarray_checksums(const entries_v1_t *inp); +/** Change the length of the variable-length array field checksums of + * 'inp' to 'newlen'.Fill extra elements with NULL; free removed + * elements. Return 0 on success; return -1 and set the error code on + * 'inp' on failure. + */ +int entries_v1_setlen_checksums(entries_v1_t *inp, size_t newlen); /** Return a newly allocated inclusion_proof_v1 with all elements set * to zero. */ @@ -640,6 +873,15 @@ const char *inclusion_proof_v1_check(const inclusion_proof_v1_t *obj); * functions. Return true iff errors were cleared. */ int inclusion_proof_v1_clear_errors(inclusion_proof_v1_t *obj); +/** Return the value of the magic field of the inclusion_proof_v1_t in + * 'inp' + */ +uint64_t inclusion_proof_v1_get_magic(const inclusion_proof_v1_t *inp); +/** Set the value of the magic field of the inclusion_proof_v1_t in + * 'inp' to 'val'. Return 0 on success; return -1 and set the error + * code on 'inp' on failure. + */ +int inclusion_proof_v1_set_magic(inclusion_proof_v1_t *inp, uint64_t val); /** Return the value of the format field of the inclusion_proof_v1_t * in 'inp' */ @@ -649,24 +891,31 @@ uint64_t inclusion_proof_v1_get_format(const inclusion_proof_v1_t *inp); * code on 'inp' on failure. */ int inclusion_proof_v1_set_format(inclusion_proof_v1_t *inp, uint64_t val); -/** Return the value of the identifier field of the - * inclusion_proof_v1_t in 'inp' +/** Return the (constant) length of the array holding the identifier + * field of the inclusion_proof_v1_t in 'inp'. */ -struct ed25519_v1_st * inclusion_proof_v1_get_identifier(inclusion_proof_v1_t *inp); +size_t inclusion_proof_v1_getlen_identifier(const inclusion_proof_v1_t *inp); +/** Return the element at position 'idx' of the fixed array field + * identifier of the inclusion_proof_v1_t in 'inp'. + */ +uint8_t inclusion_proof_v1_get_identifier(inclusion_proof_v1_t *inp, size_t idx); /** As inclusion_proof_v1_get_identifier, but take and return a const * pointer */ -const struct ed25519_v1_st * inclusion_proof_v1_getconst_identifier(const inclusion_proof_v1_t *inp); -/** Set the value of the identifier field of the inclusion_proof_v1_t - * in 'inp' to 'val'. Free the old value if any. Steals the - * referenceto 'val'.Return 0 on success; return -1 and set the error - * code on 'inp' on failure. +uint8_t inclusion_proof_v1_getconst_identifier(const inclusion_proof_v1_t *inp, size_t idx); +/** Change the element at position 'idx' of the fixed array field + * identifier of the inclusion_proof_v1_t in 'inp', so that it will + * hold the value 'elt'. */ -int inclusion_proof_v1_set_identifier(inclusion_proof_v1_t *inp, struct ed25519_v1_st *val); -/** As inclusion_proof_v1_set_identifier, but does not free the - * previous value. +int inclusion_proof_v1_set_identifier(inclusion_proof_v1_t *inp, size_t idx, uint8_t elt); +/** Return a pointer to the 32-element array field identifier of + * 'inp'. */ -int inclusion_proof_v1_set0_identifier(inclusion_proof_v1_t *inp, struct ed25519_v1_st *val); +uint8_t * inclusion_proof_v1_getarray_identifier(inclusion_proof_v1_t *inp); +/** As inclusion_proof_v1_get_identifier, but take and return a const + * pointer + */ +const uint8_t * inclusion_proof_v1_getconstarray_identifier(const inclusion_proof_v1_t *inp); /** Return the value of the tree_size field of the * inclusion_proof_v1_t in 'inp' */ @@ -685,15 +934,15 @@ uint64_t inclusion_proof_v1_get_leaf_index(const inclusion_proof_v1_t *inp); * code on 'inp' on failure. */ int inclusion_proof_v1_set_leaf_index(inclusion_proof_v1_t *inp, uint64_t val); -/** Return the value of the length field of the inclusion_proof_v1_t +/** Return the value of the n_items field of the inclusion_proof_v1_t * in 'inp' */ -uint64_t inclusion_proof_v1_get_length(const inclusion_proof_v1_t *inp); -/** Set the value of the length field of the inclusion_proof_v1_t in +uint64_t inclusion_proof_v1_get_n_items(const inclusion_proof_v1_t *inp); +/** Set the value of the n_items field of the inclusion_proof_v1_t in * 'inp' to 'val'. Return 0 on success; return -1 and set the error * code on 'inp' on failure. */ -int inclusion_proof_v1_set_length(inclusion_proof_v1_t *inp, uint64_t val); +int inclusion_proof_v1_set_n_items(inclusion_proof_v1_t *inp, uint64_t val); /** Return the length of the dynamic array holding the hashes field of * the inclusion_proof_v1_t in 'inp'. */ @@ -701,31 +950,36 @@ size_t inclusion_proof_v1_getlen_hashes(const inclusion_proof_v1_t *inp); /** Return the element at position 'idx' of the dynamic array field * hashes of the inclusion_proof_v1_t in 'inp'. */ -uint8_t inclusion_proof_v1_get_hashes(inclusion_proof_v1_t *inp, size_t idx); +struct hash_st * inclusion_proof_v1_get_hashes(inclusion_proof_v1_t *inp, size_t idx); /** As inclusion_proof_v1_get_hashes, but take and return a const * pointer */ -uint8_t inclusion_proof_v1_getconst_hashes(const inclusion_proof_v1_t *inp, size_t idx); + const struct hash_st * inclusion_proof_v1_getconst_hashes(const inclusion_proof_v1_t *inp, size_t idx); /** Change the element at position 'idx' of the dynamic array field * hashes of the inclusion_proof_v1_t in 'inp', so that it will hold - * the value 'elt'. + * the value 'elt'. Free the previous value, if any. */ -int inclusion_proof_v1_set_hashes(inclusion_proof_v1_t *inp, size_t idx, uint8_t elt); +int inclusion_proof_v1_set_hashes(inclusion_proof_v1_t *inp, size_t idx, struct hash_st * elt); +/** As inclusion_proof_v1_set_hashes, but does not free the previous + * value. + */ +int inclusion_proof_v1_set0_hashes(inclusion_proof_v1_t *inp, size_t idx, struct hash_st * elt); /** Append a new element 'elt' to the dynamic array field hashes of * the inclusion_proof_v1_t in 'inp'. */ -int inclusion_proof_v1_add_hashes(inclusion_proof_v1_t *inp, uint8_t elt); +int inclusion_proof_v1_add_hashes(inclusion_proof_v1_t *inp, struct hash_st * elt); /** Return a pointer to the variable-length array field hashes of * 'inp'. */ -uint8_t * inclusion_proof_v1_getarray_hashes(inclusion_proof_v1_t *inp); +struct hash_st * * inclusion_proof_v1_getarray_hashes(inclusion_proof_v1_t *inp); /** As inclusion_proof_v1_get_hashes, but take and return a const * pointer */ -const uint8_t * inclusion_proof_v1_getconstarray_hashes(const inclusion_proof_v1_t *inp); +const struct hash_st * const * inclusion_proof_v1_getconstarray_hashes(const inclusion_proof_v1_t *inp); /** Change the length of the variable-length array field hashes of - * 'inp' to 'newlen'.Fill extra elements with 0. Return 0 on success; - * return -1 and set the error code on 'inp' on failure. + * 'inp' to 'newlen'.Fill extra elements with NULL; free removed + * elements. Return 0 on success; return -1 and set the error code on + * 'inp' on failure. */ int inclusion_proof_v1_setlen_hashes(inclusion_proof_v1_t *inp, size_t newlen); /** Return a newly allocated request_v1 with all elements set to zero. @@ -762,6 +1016,14 @@ const char *request_v1_check(const request_v1_t *obj); * functions. Return true iff errors were cleared. */ int request_v1_clear_errors(request_v1_t *obj); +/** Return the value of the magic field of the request_v1_t in 'inp' + */ +uint64_t request_v1_get_magic(const request_v1_t *inp); +/** Set the value of the magic field of the request_v1_t in 'inp' to + * 'val'. Return 0 on success; return -1 and set the error code on + * 'inp' on failure. + */ +int request_v1_set_magic(request_v1_t *inp, uint64_t val); /** Return the value of the format field of the request_v1_t in 'inp' */ uint64_t request_v1_get_format(const request_v1_t *inp); @@ -824,162 +1086,150 @@ int request_v1_set_request_get_consistency_proof(request_v1_t *inp, struct req_g * the previous value. */ int request_v1_set0_request_get_consistency_proof(request_v1_t *inp, struct req_get_consistency_proof_v1_st *val); -/** Return a newly allocated signed_checksum32_ed25519_v1 with all - * elements set to zero. +/** Return a newly allocated signed_tree_head_v1 with all elements set + * to zero. */ -signed_checksum32_ed25519_v1_t *signed_checksum32_ed25519_v1_new(void); -/** Release all storage held by the signed_checksum32_ed25519_v1 in - * 'victim'. (Do nothing if 'victim' is NULL.) +signed_tree_head_v1_t *signed_tree_head_v1_new(void); +/** Release all storage held by the signed_tree_head_v1 in 'victim'. + * (Do nothing if 'victim' is NULL.) */ -void signed_checksum32_ed25519_v1_free(signed_checksum32_ed25519_v1_t *victim); -/** Try to parse a signed_checksum32_ed25519_v1 from the buffer in - * 'input', using up to 'len_in' bytes from the input buffer. On - * success, return the number of bytes consumed and set *output to the - * newly allocated signed_checksum32_ed25519_v1_t. On failure, return - * -2 if the input appears truncated, and -1 if the input is otherwise - * invalid. +void signed_tree_head_v1_free(signed_tree_head_v1_t *victim); +/** Try to parse a signed_tree_head_v1 from the buffer in 'input', + * using up to 'len_in' bytes from the input buffer. On success, + * return the number of bytes consumed and set *output to the newly + * allocated signed_tree_head_v1_t. On failure, return -2 if the input + * appears truncated, and -1 if the input is otherwise invalid. */ -ssize_t signed_checksum32_ed25519_v1_parse(signed_checksum32_ed25519_v1_t **output, const uint8_t *input, const size_t len_in); +ssize_t signed_tree_head_v1_parse(signed_tree_head_v1_t **output, const uint8_t *input, const size_t len_in); /** Return the number of bytes we expect to need to encode the - * signed_checksum32_ed25519_v1 in 'obj'. On failure, return a - * negative value. Note that this value may be an overestimate, and - * can even be an underestimate for certain unencodeable objects. + * signed_tree_head_v1 in 'obj'. On failure, return a negative value. + * Note that this value may be an overestimate, and can even be an + * underestimate for certain unencodeable objects. */ -ssize_t signed_checksum32_ed25519_v1_encoded_len(const signed_checksum32_ed25519_v1_t *obj); -/** Try to encode the signed_checksum32_ed25519_v1 from 'input' into - * the buffer at 'output', using up to 'avail' bytes of the output - * buffer. On success, return the number of bytes used. On failure, - * return -2 if the buffer was not long enough, and -1 if the input - * was invalid. +ssize_t signed_tree_head_v1_encoded_len(const signed_tree_head_v1_t *obj); +/** Try to encode the signed_tree_head_v1 from 'input' into the buffer + * at 'output', using up to 'avail' bytes of the output buffer. On + * success, return the number of bytes used. On failure, return -2 if + * the buffer was not long enough, and -1 if the input was invalid. */ -ssize_t signed_checksum32_ed25519_v1_encode(uint8_t *output, size_t avail, const signed_checksum32_ed25519_v1_t *input); -/** Check whether the internal state of the - * signed_checksum32_ed25519_v1 in 'obj' is consistent. Return NULL if - * it is, and a short message if it is not. +ssize_t signed_tree_head_v1_encode(uint8_t *output, size_t avail, const signed_tree_head_v1_t *input); +/** Check whether the internal state of the signed_tree_head_v1 in + * 'obj' is consistent. Return NULL if it is, and a short message if + * it is not. */ -const char *signed_checksum32_ed25519_v1_check(const signed_checksum32_ed25519_v1_t *obj); +const char *signed_tree_head_v1_check(const signed_tree_head_v1_t *obj); /** Clear any errors that were set on the object 'obj' by its setter * functions. Return true iff errors were cleared. */ -int signed_checksum32_ed25519_v1_clear_errors(signed_checksum32_ed25519_v1_t *obj); -/** Return the value of the format field of the - * signed_checksum32_ed25519_v1_t in 'inp' - */ -uint64_t signed_checksum32_ed25519_v1_get_format(const signed_checksum32_ed25519_v1_t *inp); -/** Set the value of the format field of the - * signed_checksum32_ed25519_v1_t in 'inp' to 'val'. Return 0 on - * success; return -1 and set the error code on 'inp' on failure. - */ -int signed_checksum32_ed25519_v1_set_format(signed_checksum32_ed25519_v1_t *inp, uint64_t val); -/** Return the (constant) length of the array holding the checksum - * field of the signed_checksum32_ed25519_v1_t in 'inp'. - */ -size_t signed_checksum32_ed25519_v1_getlen_checksum(const signed_checksum32_ed25519_v1_t *inp); -/** Return the element at position 'idx' of the fixed array field - * checksum of the signed_checksum32_ed25519_v1_t in 'inp'. +int signed_tree_head_v1_clear_errors(signed_tree_head_v1_t *obj); +/** Return the value of the magic field of the signed_tree_head_v1_t + * in 'inp' */ -uint8_t signed_checksum32_ed25519_v1_get_checksum(signed_checksum32_ed25519_v1_t *inp, size_t idx); -/** As signed_checksum32_ed25519_v1_get_checksum, but take and return - * a const pointer +uint64_t signed_tree_head_v1_get_magic(const signed_tree_head_v1_t *inp); +/** Set the value of the magic field of the signed_tree_head_v1_t in + * 'inp' to 'val'. Return 0 on success; return -1 and set the error + * code on 'inp' on failure. */ -uint8_t signed_checksum32_ed25519_v1_getconst_checksum(const signed_checksum32_ed25519_v1_t *inp, size_t idx); -/** Change the element at position 'idx' of the fixed array field - * checksum of the signed_checksum32_ed25519_v1_t in 'inp', so that it - * will hold the value 'elt'. +int signed_tree_head_v1_set_magic(signed_tree_head_v1_t *inp, uint64_t val); +/** Return the value of the format field of the signed_tree_head_v1_t + * in 'inp' */ -int signed_checksum32_ed25519_v1_set_checksum(signed_checksum32_ed25519_v1_t *inp, size_t idx, uint8_t elt); -/** Return a pointer to the 32-element array field checksum of 'inp'. +uint64_t signed_tree_head_v1_get_format(const signed_tree_head_v1_t *inp); +/** Set the value of the format field of the signed_tree_head_v1_t in + * 'inp' to 'val'. Return 0 on success; return -1 and set the error + * code on 'inp' on failure. */ -uint8_t * signed_checksum32_ed25519_v1_getarray_checksum(signed_checksum32_ed25519_v1_t *inp); -/** As signed_checksum32_ed25519_v1_get_checksum, but take and return - * a const pointer +int signed_tree_head_v1_set_format(signed_tree_head_v1_t *inp, uint64_t val); +/** Return the value of the timestamp field of the + * signed_tree_head_v1_t in 'inp' */ -const uint8_t * signed_checksum32_ed25519_v1_getconstarray_checksum(const signed_checksum32_ed25519_v1_t *inp); -/** Return the value of the length field of the - * signed_checksum32_ed25519_v1_t in 'inp' +uint64_t signed_tree_head_v1_get_timestamp(const signed_tree_head_v1_t *inp); +/** Set the value of the timestamp field of the signed_tree_head_v1_t + * in 'inp' to 'val'. Return 0 on success; return -1 and set the error + * code on 'inp' on failure. */ -uint64_t signed_checksum32_ed25519_v1_get_length(const signed_checksum32_ed25519_v1_t *inp); -/** Set the value of the length field of the - * signed_checksum32_ed25519_v1_t in 'inp' to 'val'. Return 0 on - * success; return -1 and set the error code on 'inp' on failure. +int signed_tree_head_v1_set_timestamp(signed_tree_head_v1_t *inp, uint64_t val); +/** Return the value of the tree_size field of the + * signed_tree_head_v1_t in 'inp' */ -int signed_checksum32_ed25519_v1_set_length(signed_checksum32_ed25519_v1_t *inp, uint64_t val); -/** Return the length of the dynamic array holding the identifier - * field of the signed_checksum32_ed25519_v1_t in 'inp'. +uint64_t signed_tree_head_v1_get_tree_size(const signed_tree_head_v1_t *inp); +/** Set the value of the tree_size field of the signed_tree_head_v1_t + * in 'inp' to 'val'. Return 0 on success; return -1 and set the error + * code on 'inp' on failure. */ -size_t signed_checksum32_ed25519_v1_getlen_identifier(const signed_checksum32_ed25519_v1_t *inp); -/** Return the element at position 'idx' of the dynamic array field - * identifier of the signed_checksum32_ed25519_v1_t in 'inp'. +int signed_tree_head_v1_set_tree_size(signed_tree_head_v1_t *inp, uint64_t val); +/** Return the (constant) length of the array holding the root_hash + * field of the signed_tree_head_v1_t in 'inp'. */ -uint8_t signed_checksum32_ed25519_v1_get_identifier(signed_checksum32_ed25519_v1_t *inp, size_t idx); -/** As signed_checksum32_ed25519_v1_get_identifier, but take and - * return a const pointer +size_t signed_tree_head_v1_getlen_root_hash(const signed_tree_head_v1_t *inp); +/** Return the element at position 'idx' of the fixed array field + * root_hash of the signed_tree_head_v1_t in 'inp'. */ -uint8_t signed_checksum32_ed25519_v1_getconst_identifier(const signed_checksum32_ed25519_v1_t *inp, size_t idx); -/** Change the element at position 'idx' of the dynamic array field - * identifier of the signed_checksum32_ed25519_v1_t in 'inp', so that - * it will hold the value 'elt'. +uint8_t signed_tree_head_v1_get_root_hash(signed_tree_head_v1_t *inp, size_t idx); +/** As signed_tree_head_v1_get_root_hash, but take and return a const + * pointer */ -int signed_checksum32_ed25519_v1_set_identifier(signed_checksum32_ed25519_v1_t *inp, size_t idx, uint8_t elt); -/** Append a new element 'elt' to the dynamic array field identifier - * of the signed_checksum32_ed25519_v1_t in 'inp'. +uint8_t signed_tree_head_v1_getconst_root_hash(const signed_tree_head_v1_t *inp, size_t idx); +/** Change the element at position 'idx' of the fixed array field + * root_hash of the signed_tree_head_v1_t in 'inp', so that it will + * hold the value 'elt'. */ -int signed_checksum32_ed25519_v1_add_identifier(signed_checksum32_ed25519_v1_t *inp, uint8_t elt); -/** Return a pointer to the variable-length array field identifier of - * 'inp'. +int signed_tree_head_v1_set_root_hash(signed_tree_head_v1_t *inp, size_t idx, uint8_t elt); +/** Return a pointer to the 32-element array field root_hash of 'inp'. */ -uint8_t * signed_checksum32_ed25519_v1_getarray_identifier(signed_checksum32_ed25519_v1_t *inp); -/** As signed_checksum32_ed25519_v1_get_identifier, but take and - * return a const pointer +uint8_t * signed_tree_head_v1_getarray_root_hash(signed_tree_head_v1_t *inp); +/** As signed_tree_head_v1_get_root_hash, but take and return a const + * pointer */ -const uint8_t * signed_checksum32_ed25519_v1_getconstarray_identifier(const signed_checksum32_ed25519_v1_t *inp); -/** Change the length of the variable-length array field identifier of - * 'inp' to 'newlen'.Fill extra elements with 0. Return 0 on success; - * return -1 and set the error code on 'inp' on failure. +const uint8_t * signed_tree_head_v1_getconstarray_root_hash(const signed_tree_head_v1_t *inp); +/** Return the value of the n_items field of the signed_tree_head_v1_t + * in 'inp' */ -int signed_checksum32_ed25519_v1_setlen_identifier(signed_checksum32_ed25519_v1_t *inp, size_t newlen); -/** Return the (constant) length of the array holding the signature - * field of the signed_checksum32_ed25519_v1_t in 'inp'. +uint64_t signed_tree_head_v1_get_n_items(const signed_tree_head_v1_t *inp); +/** Set the value of the n_items field of the signed_tree_head_v1_t in + * 'inp' to 'val'. Return 0 on success; return -1 and set the error + * code on 'inp' on failure. */ -size_t signed_checksum32_ed25519_v1_getlen_signature(const signed_checksum32_ed25519_v1_t *inp); -/** Return the element at position 'idx' of the fixed array field - * signature of the signed_checksum32_ed25519_v1_t in 'inp'. +int signed_tree_head_v1_set_n_items(signed_tree_head_v1_t *inp, uint64_t val); +/** Return the length of the dynamic array holding the signatures + * field of the signed_tree_head_v1_t in 'inp'. */ -uint8_t signed_checksum32_ed25519_v1_get_signature(signed_checksum32_ed25519_v1_t *inp, size_t idx); -/** As signed_checksum32_ed25519_v1_get_signature, but take and return - * a const pointer +size_t signed_tree_head_v1_getlen_signatures(const signed_tree_head_v1_t *inp); +/** Return the element at position 'idx' of the dynamic array field + * signatures of the signed_tree_head_v1_t in 'inp'. */ -uint8_t signed_checksum32_ed25519_v1_getconst_signature(const signed_checksum32_ed25519_v1_t *inp, size_t idx); -/** Change the element at position 'idx' of the fixed array field - * signature of the signed_checksum32_ed25519_v1_t in 'inp', so that - * it will hold the value 'elt'. +struct sigident_ed25519_st * signed_tree_head_v1_get_signatures(signed_tree_head_v1_t *inp, size_t idx); +/** As signed_tree_head_v1_get_signatures, but take and return a const + * pointer */ -int signed_checksum32_ed25519_v1_set_signature(signed_checksum32_ed25519_v1_t *inp, size_t idx, uint8_t elt); -/** Return a pointer to the 64-element array field signature of 'inp'. + const struct sigident_ed25519_st * signed_tree_head_v1_getconst_signatures(const signed_tree_head_v1_t *inp, size_t idx); +/** Change the element at position 'idx' of the dynamic array field + * signatures of the signed_tree_head_v1_t in 'inp', so that it will + * hold the value 'elt'. Free the previous value, if any. */ -uint8_t * signed_checksum32_ed25519_v1_getarray_signature(signed_checksum32_ed25519_v1_t *inp); -/** As signed_checksum32_ed25519_v1_get_signature, but take and return - * a const pointer +int signed_tree_head_v1_set_signatures(signed_tree_head_v1_t *inp, size_t idx, struct sigident_ed25519_st * elt); +/** As signed_tree_head_v1_set_signatures, but does not free the + * previous value. */ -const uint8_t * signed_checksum32_ed25519_v1_getconstarray_signature(const signed_checksum32_ed25519_v1_t *inp); -/** Return the value of the namespace field of the - * signed_checksum32_ed25519_v1_t in 'inp' +int signed_tree_head_v1_set0_signatures(signed_tree_head_v1_t *inp, size_t idx, struct sigident_ed25519_st * elt); +/** Append a new element 'elt' to the dynamic array field signatures + * of the signed_tree_head_v1_t in 'inp'. */ -struct ed25519_v1_st * signed_checksum32_ed25519_v1_get_namespace(signed_checksum32_ed25519_v1_t *inp); -/** As signed_checksum32_ed25519_v1_get_namespace, but take and return - * a const pointer +int signed_tree_head_v1_add_signatures(signed_tree_head_v1_t *inp, struct sigident_ed25519_st * elt); +/** Return a pointer to the variable-length array field signatures of + * 'inp'. */ -const struct ed25519_v1_st * signed_checksum32_ed25519_v1_getconst_namespace(const signed_checksum32_ed25519_v1_t *inp); -/** Set the value of the namespace field of the - * signed_checksum32_ed25519_v1_t in 'inp' to 'val'. Free the old - * value if any. Steals the referenceto 'val'.Return 0 on success; - * return -1 and set the error code on 'inp' on failure. +struct sigident_ed25519_st * * signed_tree_head_v1_getarray_signatures(signed_tree_head_v1_t *inp); +/** As signed_tree_head_v1_get_signatures, but take and return a const + * pointer */ -int signed_checksum32_ed25519_v1_set_namespace(signed_checksum32_ed25519_v1_t *inp, struct ed25519_v1_st *val); -/** As signed_checksum32_ed25519_v1_set_namespace, but does not free - * the previous value. +const struct sigident_ed25519_st * const * signed_tree_head_v1_getconstarray_signatures(const signed_tree_head_v1_t *inp); +/** Change the length of the variable-length array field signatures of + * 'inp' to 'newlen'.Fill extra elements with NULL; free removed + * elements. Return 0 on success; return -1 and set the error code on + * 'inp' on failure. */ -int signed_checksum32_ed25519_v1_set0_namespace(signed_checksum32_ed25519_v1_t *inp, struct ed25519_v1_st *val); +int signed_tree_head_v1_setlen_signatures(signed_tree_head_v1_t *inp, size_t newlen); #endif diff --git a/trunnel/stfe.trunnel b/trunnel/stfe.trunnel index eb82942..8a26d92 100644 --- a/trunnel/stfe.trunnel +++ b/trunnel/stfe.trunnel @@ -1,11 +1,15 @@ +/* always POST for consistency? */ + +const MAGIC_V1 = 0x535446455f5f5631; /* "STFE__V1" */ + const T_GET_ENTRIES_V1 = 1; const T_GET_PROOF_BY_HASH_V1 = 2; const T_GET_CONSISTENCY_PROOF_V1 = 3; -const T_INCLUSION_PROOF_V1 = 4; -const T_CONSISTENCY_PROOF_V1 = 5; -const T_SIGNED_TREE_HEAD_V1 = 6; -const T_SIGNED_CHECKSUM32_ED25519_V1 = 7; -const T_ED25519_V1 = 8; + +const T_ENTRIES_V1 = 4; +const T_INCLUSION_PROOF_V1 = 5; +const T_CONSISTENCY_PROOF_V1 = 6; +const T_SIGNED_TREE_HEAD_V1 = 7; struct req_get_entries_v1 { u64 start_size; @@ -23,6 +27,7 @@ struct req_get_consistency_proof_v1 { }; struct request_v1 { + u64 magic IN [ MAGIC_V1 ]; u64 format IN [ T_GET_ENTRIES_V1, T_GET_PROOF_BY_HASH_V1, T_GET_CONSISTENCY_PROOF_V1 ]; union request[format] { @@ -33,53 +38,57 @@ struct request_v1 { }; } +struct sigident_ed25519 { + u8 signature[64]; + u8 identifier[32]; +}; + +struct hash { + u8 hash[32]; +}; + +struct signed_checksum32_ed25519 { + u8 checksum[32]; + u64 length IN [ 1..128 ]; + u8 identifier[length]; + u8 signature[64]; + u8 namespace[32]; +}; + +struct entries_v1 { + u64 magic IN [ MAGIC_V1 ]; + u64 format IN [ T_ENTRIES_V1 ]; + u64 n_items; + struct signed_checksum32_ed25519 checksums[n_items]; +}; + struct inclusion_proof_v1 { + u64 magic IN [ MAGIC_V1 ]; u64 format IN [ T_INCLUSION_PROOF_V1 ]; - struct ed25519_v1 identifier; + u8 identifier[32]; u64 tree_size; u64 leaf_index; - u64 length; /* TODO: constraint: multiple of 32 */ - u8 hashes[length]; + u64 n_items; + struct hash hashes[n_items]; }; struct consistency_proof_v1 { + u64 magic IN [ MAGIC_V1 ]; u64 format IN [ T_CONSISTENCY_PROOF_V1 ]; - struct ed25519_v1 identifier; + u8 identifier[32]; u64 old_size; u64 new_size; - u64 length; /* TODO: constraint: multiple of 32 */ - u8 hashes[length]; + u64 n_items; + struct hash hashes[n_items]; }; -/* Not used -struct sigident { - u8 signature[64]; - struct ed25519_v1 identifier; -}; */ - struct signed_tree_head_v1 { + u64 magic IN [ MAGIC_V1 ]; u64 format IN [ T_SIGNED_TREE_HEAD_V1 ]; u64 timestamp; u64 tree_size; u8 root_hash[32]; - u64 length; /* TODO: constraint: multiple of 104 */ - u8 sigident[length]; - /* Alternatively, if we would chose to replace length with n_items: u64 n_items; - struct sigident[n_items]; */ -}; - -struct signed_checksum32_ed25519_v1 { - u64 format IN [ T_SIGNED_CHECKSUM32_ED25519_V1 ]; - u8 checksum[32]; - u64 length IN [ 1..127 ]; /* The spec contradicts itself on this point -- is it 127 or 128? */ - u8 identifier[length]; - u8 signature[64]; - struct ed25519_v1 namespace; -}; - -struct ed25519_v1 { - u64 format IN [ T_ED25519_V1 ]; - u8 pubkey[32]; + struct sigident_ed25519 signatures[n_items]; }; -- cgit v1.2.3 From 24cc6b0db8ef9c718925d14b329f21938e5d2b1b Mon Sep 17 00:00:00 2001 From: Rasmus Dahlberg Date: Tue, 20 Apr 2021 12:28:28 +0200 Subject: started on our in-progress (re)design documents --- doc/api.md | 247 ++++++++++++++++++++ doc/design.md | 32 +++ doc/formats.md | 160 ------------- doc/schema/consistency_proof.schema.json | 30 +++ doc/schema/example/consistency_proof.json | 7 + doc/schema/example/inclusion_proof.json | 7 + doc/schema/example/leaves.json | 14 ++ doc/schema/example/sth.json | 11 + doc/schema/inclusion_proof.schema.json | 30 +++ doc/schema/leaves.schema.json | 38 +++ doc/schema/sth.schema.json | 50 ++++ doc/sketch.md | 372 ------------------------------ 12 files changed, 466 insertions(+), 532 deletions(-) create mode 100644 doc/api.md create mode 100644 doc/design.md delete mode 100644 doc/formats.md create mode 100644 doc/schema/consistency_proof.schema.json create mode 100644 doc/schema/example/consistency_proof.json create mode 100644 doc/schema/example/inclusion_proof.json create mode 100644 doc/schema/example/leaves.json create mode 100644 doc/schema/example/sth.json create mode 100644 doc/schema/inclusion_proof.schema.json create mode 100644 doc/schema/leaves.schema.json create mode 100644 doc/schema/sth.schema.json delete mode 100644 doc/sketch.md diff --git a/doc/api.md b/doc/api.md new file mode 100644 index 0000000..760663b --- /dev/null +++ b/doc/api.md @@ -0,0 +1,247 @@ +# System Transparency Logging: API v0 +This document describes details of the System Transparency logging API, +version 0. The broader picture is not explained here. We assume that you have +read the System Transparency design document. It can be found [here](https://github.com/system-transparency/stfe/blob/design/doc/design.md). + +**Warning.** +This is a work-in-progress document that may be moved or modified. + +## Overview +The log implements an HTTP(S) API: +- Requests that add data to the log use the HTTP POST method. The HTTP content +type is `application/x-www-form-urlencoded`. The posted data are key-value +pairs. Binary data must be base64-encoded. +- Requests that retrieve data from the log use the HTTP GET method. The HTTP +content type is `application/x-www-form-urlencoded`. Input parameters are +key-value pairs. +- Responses are JSON objects. The HTTP content type is `application/json`. +- Error messages are human-readable strings. The HTTP content type is +`text/plain`. + +We decided to use these web formats for requests and responses because the log +is running as an HTTP(S) service. In other words, anyone that interacts with +the log is most likely using these formats already. The other benefit is that +all requests and responses are human-readable. This makes it easier to +understand the protocol, troubleshoot issues, and copy-paste. We favored +compatibility and understandability over a wire-efficient format. + +Note that we are not using JSON for signed and/or logged data. In other words, +a submitter that wishes to distribute log responses to their user base in a +different format may do so. The forced (de)serialization parser on _end-users_ +is a small subset of Trunnel. Trunnel is an "idiot-proof" wire-format that the +Tor project uses. + +## Primitives +### Cryptography +The log uses the same Merkle tree hash strategy as [RFC 6962, §2](https://tools.ietf.org/html/rfc6962#section-2). +The hash functions must be [SHA256](https://csrc.nist.gov/csrc/media/publications/fips/180/4/final/documents/fips180-4-draft-aug2014.pdf). +The log must sign tree heads using [Ed25519](https://tools.ietf.org/html/rfc8032). +The log's witnesses must also sign tree heads using Ed25519. + +All other parts that are not Merkle tree related also use SHA256 as the hash +function. Using more than one hash function would increases the overall attack +surface: two hash functions must be collision resistant instead of one. + +We recommend that submitters sign using Ed25519. We also support RSA with +[deterministic](https://tools.ietf.org/html/rfc8017#section-8.2) +or [probabilistic](https://tools.ietf.org/html/rfc8017#section-8.1) +padding. Supporting RSA is suboptimal, but excluding it would make the log +useless for many possible adopters. + +### Serialization +We use the [Trunnel](https://gitweb.torproject.org/trunnel.git) [description language](https://www.seul.org/~nickm/trunnel-manual.html) +to define (de)serialization of data structures that need to be signed or +inserted into the Merkle tree. Trunnel is more expressive than the +[SSH wire format](https://tools.ietf.org/html/rfc4251#section-5). +It is about as expressive as the [TLS presentation language](https://tools.ietf.org/html/rfc8446#section-3). +A notable difference is that Trunnel supports integer constraints. The Trunnel +language is also readable by humans _and_ machines. "Obviously correct code" +can be generated in C and Go. + +A fair summary of our Trunnel usage is as follows. + +All integers are 64-bit, unsigned, and in network byte order. A fixed-size byte +array is put into the serialization buffer in-order, starting from the first +byte. These basic types are concatenated to form a collection. You should not +need a general-purpose Trunnel (de)serialization parser to work with this +format. If you have one, you may use it though. The main point of using +Trunnel is that it makes a simple format explicit and unambiguous. + +TODO: URL-encode _or_ JSON? I think we should only need one. Always doing HTTP +POST would also ensure that input parameters don't show up in web server logs. + +#### Merkle tree head +Tree heads are signed by the log and its witnesses. It contains a timestamp, a +tree size, and a root hash. The timestamp is included so that monitors can +ensure _liveliness_. It is the time since the UNIX epoch (January 1, 1970 +00:00:00 UTC) in milliseconds. The tree size specifies the current number of +leaves. The root hash fixes the structure and content of the Merkle tree. + +``` +struct tree_head { + u64 timestamp; + u64 tree_size; + u8 root_hash[32]; +}; +``` + +The serialized tree head must be signed using Ed25519. A witness must only sign +the log's tree head if it is consistent with prior history and the timestamp is +roughly correct. A timestamp is roughly correct if it is not backdated or +future-dated more than 12 hours. + +#### Merkle tree leaf +The log supports a single leaf type. It contains a checksum, a signature +scheme, a signature that the submitter computed over that checksum, and the hash +of the public verification key that can be used to verify the signature. + +``` +const ALG_ED25519 = 1; // RFC 8032 +const ALG_RSASSA_PKCS1_V1_5 = 2; // RFC 8017 +const ALG_RSASSA_PSS = 3; // RFC 8017 + +struct tree_leaf { + u8 checksum[32]; + u64 signature_scheme IN [ + ALG_ED25519, + ALG_RSASSA_PKCS1_V1_5, + ALG_RSASSA_PSS, + ]; + union signature[signature_scheme] { + ALG_ED25519: u8 ed25519[32]; + default: u8 rsa[512]; + } + u8 key_hash[32]; +} +``` + +A key-hash is included in the leaf so that it can be attributed to the signing +entity. A hash, rather than the full public verification key, is used to force +the verifier to locate the appropriate key and make an explicit trust decision. + +## Public endpoints +Every log has a base URL that identifies it uniquely. The only constraint is +that it must be a valid HTTP(S) URL that can have the `/st/v0/` suffix +appended. For example, a complete endpoint URL could be +`https://log.example.com/2021/st/v0/get-signed-tree-head`. + +### get-signed-tree-head +``` +GET /st/v0/get-signed-tree-head +``` + +Input key-value pairs: +- `type`: either the string "latest", "stable", or "cosigned". + - "latest": ask for the most recent signed tree head. + - "stable": ask for a recent signed tree head that is fixed for some period + of time. + - "cosigned": ask for a recent cosigned tree head. + +Output: +- On success: status 200 OK and a signed tree head. The response body is +defined by the following [schema](https://github.com/system-transparency/stfe/blob/design/doc/schema/sth.schema.json). +- On failure: a different status code and a human-readable error message. + +### get-proof-by-hash +``` +POST /st/v0/get-proof-by-hash +``` + +Input key-value pairs: +- `leaf_hash`: a base64-encoded leaf hash that identifies which `tree_leaf` the +log should prove inclusion for. The leaf hash is computed using the RFC 6962 +hashing strategy. In other words, `H(0x00 | tree_leaf)`. +- `tree_size`: the tree size of a tree head that the proof should be based on. + +Output: +- On success: status 200 OK and an inclusion proof. The response body is +defined by the following [schema](https://github.com/system-transparency/stfe/blob/design/doc/schema/inclusion_proof.schema.json). +- On failure: a different status code and a human-readable error message. + +### get-consistency-proof +``` +POST /st/v0/get-consistency-proof +``` + +Input key-value pairs: +- `new_size`: the tree size of a newer tree head. +- `old_size`: the tree size of an older tree head that the log should prove is +consistent with the newer tree head. + +Output: +- On success: status 200 OK and a consistency proof. The response body is +defined by the following [schema](https://github.com/system-transparency/stfe/blob/design/doc/schema/consistency_proof.schema.json). +- On failure: a different status code and a human-readable error message. + +### get-leaves +``` +POST /st/v0/get-leaves +``` + +Input key-value pairs: +- `start_size`: zero-based index of the first leaf to retrieve. +- `end_size`: index of the last leaf to retrieve. + +Output: +- On success: status 200 OK and a list of leaves. The response body is +defined by the following [schema](https://github.com/system-transparency/stfe/blob/design/doc/schema/leaves.schema.json). +- On failure: a different status code and a human-readable error message. + +The log may truncate the list of returned leaves. However, it must not be an +empty list on success. + +### add-leaf +``` +POST /st/v0/add-leaf +``` + +Input key-value pairs: +- `leaf_checksum`: the checksum that the submitter wants to log in base64. +- `signature_scheme`: the signature scheme that the submitter wants to use. +- `tree_leaf_signature`: the submitter's `tree_leaf` signature in base64. +- `verification_key`: the submitter's public verification key. It is serialized +as described in the corresponding RFC, then base64-encoded. +- `domain_hint`: a domain name that indicates where the public verification-key +hash can be downloaded in base64. Supported methods: DNS and HTTPS +(TODO: docdoc). + +Output: +- On success: HTTP 200. The log will _try_ to incorporate the submitted leaf +into its Merkle tree. +- On failure: a different status code and a human-readable error message. + +The submitted entry will not be accepted if the signature is invalid or if the +downloaded verification-key hash does not match. The submitted entry may also +not be accepted if the second-level domain name exceeded its rate limit. By +coupling every add-leaf request with a second-level domain, it becomes more +difficult to spam the log. You would need an excessive number of domain names. +This becomes costly if free domain names are rejected. + +The log does not publish domain-name to key bindings because key management is +more complex than that. + +Public logging should not be assumed until an inclusion proof is available. An +inclusion proof should not be relied upon unless it leads up to a trustworthy +signed tree head. Witness cosigning can make a tree head trustworthy. + +TODO: the log may allow no `domain_hint`? Especially useful for v0 testing. + +### add-cosignature +``` +POST /st/v0/add-cosignature +``` + +Input key-value pairs: +- `signature`: a base64-encoded signature over a `tree_head` that is fixed for +some period of time. The cosigning witness retrieves the tree head using the +`get-signed-tree-head` endpoint with the "stable" type. +- `key_hash`: a base64-encoded hash of the public verification key that can be +used to verify the signature. + +Output: +- HTTP status 200 OK on success. Otherwise a different status code and a +human-readable error message. + +The key-hash can be used to identify which witness signed the log's tree head. +A key-hash, rather than the full verification key, is used to force the verifier +to locate the appropriate key and make an explicit trust decision. diff --git a/doc/design.md b/doc/design.md new file mode 100644 index 0000000..f966d03 --- /dev/null +++ b/doc/design.md @@ -0,0 +1,32 @@ +# System Transparency Logging: Design v0 +We propose System Transparency logging. It is similar to Certificate +Transparency, expect that cryptographically signed checksums are logged as +opposed to X.509 certificates. Publicly logging signed checksums allow anyone +to discover which keys signed what. As such, malicious and unintended key-usage +can be _discovered_. We present our design and discuss how two possible +use-cases influenced it: binary transparency and reproducible builds. + +**Target audience.** +You are most likely interested in transparency logs or supply-chain security. + +**Preliminaries.** +You have basic understanding of cryptographic primitives like digital +signatures, hash functions, and Merkle trees. You roughly know what problem +Certificate Transparency solves and how. You may never have heard the term +_gossip-audit model_, or know how it is related to trust assumptions and +detectability properties. + +**Warning.** +This is a work-in-progress document that may be moved or modified. + +## Introduction +Transparency logs make it possible to detect unwanted events. For example, + are there any (mis-)issued TLS certificates [\[CT\]](https://tools.ietf.org/html/rfc6962), + did you get a different Go module than everyone else [\[ChecksumDB\]](https://go.googlesource.com/proposal/+/master/design/25530-sumdb.md), + or is someone running unexpected commands on your server [\[AuditLog\]](https://transparency.dev/application/reliably-log-all-actions-performed-on-your-servers/). +System Transparency logging makes signed checksums transparent. The goal is to +_detect_ unwanted key-usage without making assumptions about the signed data. + +## Threat model and (non-)goals + +## Design diff --git a/doc/formats.md b/doc/formats.md deleted file mode 100644 index bffd05f..0000000 --- a/doc/formats.md +++ /dev/null @@ -1,160 +0,0 @@ -# Formats -This document defines data structures and data formats. - -## Overview -Here we give an overview of our presentation language / serialization rules. - -All integers are represented by 64-bit unsigned integers in network byte order. - -Variable length lists have an integer specifying its length. Then each list -item is enumerated. - -TODO: fixme. - -## Items -Every item type start with a versioned format specifier. Protocol version 1 -uses format specifiers in the range 1--X. - -### Request data structures -Log endpoints that take input data use the following request data structures. - -#### `get_entries_v1` -``` -0 Format 8 16 24 -+----------+----------------+----------------+ -| 1 | Start Size | End Size | -+----------+----------------+----------------+ - uint64 uint64 uint64 -``` -- Format is always 1 for items of type `get_entries_v1`. -- Start size specifies the index of the first Merkle tree leaf to retrieve. -- End size specifies the index of the last Merkle tree leaf to retrieve. - -#### `get_proof_by_hash_v1` -``` -0 Format 8 16 48 -+----------+----------------+----------------+ -| 2 | Tree size | Leaf hash | -+----------+----------------+----------------+ - uint64 uint64 fixed byte array -``` -- Format is always 2 for items of type `get_proof_by_hash_v1`. -- Leaf hash is computed as described in [RFC 6962/bis, §2.1.1](https://tools.ietf.org/html/draft-ietf-trans-rfc6962-bis-35#section-2.1.1). -- Tree size specifies which Merkle tree root inclusion should be proven for. - -#### `get_consistency_proof_v1` -``` -0 Format 8 16 24 -+----------+----------------+----------------+ -| 3 | Old size | New size | -+----------+----------------+----------------+ - uint64 uint64 uint64 -``` -- Format is always 3 for items of type `get_consistency_proof_v1`. -- Old size specifies the tree size of an older Merkle tree head. -- New size specifies the tree size of a newer Merkle tree head. - -### Proof and log data structures -#### `inclusion_proof_v1` -``` - --zero or more node hashes--> -0 Format 8 48 56 64 72 72+Length -+----------+----------------+----------------+----------------+----------------+--------//--------+ -| 4 | Identifier | Tree size | Leaf index | Length | Node hashes | -+----------+----------------+----------------+----------------+----------------+--------//--------+ - uint64 ed25519_v1 uint64 uint64 uint64 list body -``` -- Format is always 4 for items of type `inclusion_proof_v1`. -- Identifier identifies the log uniquely as an `ed25519_v1` item. -- Tree size is the size of the Merkle tree that the proof is based on. -- Leaf index is a zero-based index of the log entry that the proof is based on. -- The remaining part is a list of node hashes. - - Length specifies the full byte size of the list. It must be `32 * m`, - where `m >= 0`. This means that an inclusion needs zero or more node - hashes to be well-formed. - - Node hash is a node hash in the Merkle tree that the proof is based on. - -Remark: the list of node hashes is generated and verified as in [RFC 6962/bis, -§2.1.3](https://tools.ietf.org/html/draft-ietf-trans-rfc6962-bis-35#section-2.1.3). - -#### `consistency_proof_v1` -``` - --zero or more node hashes--> -0 Format 8 48 56 64 72 72+Length -+----------+----------------+----------------+----------------+----------------+--------//--------+ -| 5 | Identifier | Old size | New size | Length | Node hashes | -+----------+----------------+----------------+----------------+----------------+--------//--------+ - uint64 ed25519_v1 uint64 uint64 uint64 list body -``` -- Format is always 5 for items of type `consistency_proof_v1`. -- Identifier identifies the log uniquely as an `ed25519_v1` item. -- Old size is the tree size of the older Merkle tree. -- New size is the tree size of the newer Merkle tree. -- The remaining part is a list of node hashes. - - Length specifies the full byte size of the list. It must be `32 * m`, - where `m >= 0`. This means that a consistenty proof needs zero or more node - hashes to be well-formed. - - Node hash is a node hash from the older or the newer Merkle tree. - -Remark: the list of node hashes is generated and verified as in [RFC 6962/bis, -§2.1.4](https://tools.ietf.org/html/draft-ietf-trans-rfc6962-bis-35#section-2.1.4). - -#### `signed_tree_head_v1` -``` - ----one or more signature-identifier pairs-------> -0 Format 8 16 24 56 64 128 168 64+Length -+----------+----------------+----------------+----------------+----------------+----------------+----------------+--//--+ -| 6 | Timestamp | Tree size | Root hash | Length | Signature | Identifier | .... | -+----------+----------------+----------------+----------------+----------------+----------------+----------------+--//--+ - uint64 uint64 uint64 fixed byte array uint64 fixed byte array ed25519_v1 cont. list body -``` -- Format is always 6 for items of type `signed_tree_head_v1`. -- Timestamp is the time since the UNIX epoch (January 1, 1970 00:00:00 UTC) in -milliseconds. -- Tree size is the number of leaves in the current Merkle tree. -- Root hash is the root hash of the current Merkle tree. -- The remaining part is a list of signature-identifier pairs. - - Length specifies the full byte size of the list. It must be `104 * m`, - where `m > 1`. This means that a signed tree head needs at least one - signature-identifier pair to be well-formed. - - Signature is an Ed25519 signature over bytes 0--56. The signature is - encodes as in [RFC 8032, §3.3](https://tools.ietf.org/html/rfc8032#section-3.3). - - Identifier identifies the signer uniquely as an `ed25519_v1` item. - -Remark: there may be multiple signature-identifier pairs if the log is cosigned. - -#### `signed_checksum32_ed25519_v1` -``` -0 Format 8 40 56 56+Length 120+Length 160+Length -+----------+----------------+----------------+-------//---------+----------------+--------//--------+ -| 7 | Checksum | Length | Identifier | Signature | Namespace | -+----------+----------------+----------------+-------//---------+----------------+--------//--------+ - uint64 fixed byte array uint64 byte array fixed byte array ed25519_v1 -``` -- Format is always 7 for items of type `signed_checksum32_ed25519_v1`. -- Checksum is a 32-byte checksum that represents a data item of opaque type. -- Length specified the full byte size of the following identifier. It must be -larger than zero and less than 128. -- Identifier identifies what the checksum represents. The aforementioned length -constraint means that the identifier cannot be omitted or exceed 128 bytes. -- Signature is an Ed25519 signature over bytes 0--56+Length. The signature is -encodes as in [RFC 8032, §3.3](https://tools.ietf.org/html/rfc8032#section-3.3). -- Namespace is an `ed25519_v1` item that identifies the signer uniquely. - -Remark: to keep this checksum entry as simple as possible it does not have a -variable length checksum or any agility with regards to the signing namespace. -This means that we need to have multiple leaf types that follow the pattern -`signed_checksum{32,64}_namespace_v1`. - -### Namespace data structures -#### `ed25519_v1` -``` -0 Format 8 40 -+----------+----------------+ -| 8 | public key | -+----------+----------------+ - uint64 fixed byte array -``` -- The format is always 8 for items of type `ed25519_v1`. -- The public Ed25519 verification key is always 32 bytes. See encoding in [RFC -8032, §3.2](https://tools.ietf.org/html/rfc8032#section-3.2). diff --git a/doc/schema/consistency_proof.schema.json b/doc/schema/consistency_proof.schema.json new file mode 100644 index 0000000..003f3c7 --- /dev/null +++ b/doc/schema/consistency_proof.schema.json @@ -0,0 +1,30 @@ +{ + "$schema": "https://json-schema.org/draft-07/schema#", + "title": "inclusion_proof", + "description": "JSON-formatted inclusion proof, version 0.", + + "type": "object", + "required": [ "new_size", "old_size", "consistency_proof" ], + "properties": { + "new_size": { + "description": "The tree size of the newer Merkle tree head.", + "type": "integer", + "minimum": 0 + }, + "old_size": { + "description": "The tree size of the older Merkle tree head.", + "type": "integer", + "minimum": 0 + }, + "consistency_proof": { + "description": "A list of base64-encoded node hashes that proves consistency", + "type": "array", + "items": { + "description": "A node hash in base64", + "type": "string", + "minLength": 44, + "maxLength": 44 + } + } + } +} diff --git a/doc/schema/example/consistency_proof.json b/doc/schema/example/consistency_proof.json new file mode 100644 index 0000000..0a323b7 --- /dev/null +++ b/doc/schema/example/consistency_proof.json @@ -0,0 +1,7 @@ +{ + "new_size": 2, + "old_size": 1, + "consistency_proof": [ + "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA=" + ] +} diff --git a/doc/schema/example/inclusion_proof.json b/doc/schema/example/inclusion_proof.json new file mode 100644 index 0000000..d46d426 --- /dev/null +++ b/doc/schema/example/inclusion_proof.json @@ -0,0 +1,7 @@ +{ + "tree_size": 2, + "leaf_index": 0, + "inclusion_proof": [ + "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA=" + ] +} diff --git a/doc/schema/example/leaves.json b/doc/schema/example/leaves.json new file mode 100644 index 0000000..1eed05d --- /dev/null +++ b/doc/schema/example/leaves.json @@ -0,0 +1,14 @@ +[ + { + "checksum": "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA=", + "signature_scheme": 1, + "signature": "CCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCC=", + "key_hash": "DDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDD=" + }, + { + "checksum": "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA=", + "signature_scheme": 2, + "signature": "BBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBB=", + "key_hash": "CCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCC=" + } +] diff --git a/doc/schema/example/sth.json b/doc/schema/example/sth.json new file mode 100644 index 0000000..ec3ad11 --- /dev/null +++ b/doc/schema/example/sth.json @@ -0,0 +1,11 @@ +{ + "timestamp": 0, + "tree_size": 0, + "root_hash": "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA=", + "signatures": [ + { + "key_hash": "BBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBB=", + "signature": "CCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCC=" + } + ] +} diff --git a/doc/schema/inclusion_proof.schema.json b/doc/schema/inclusion_proof.schema.json new file mode 100644 index 0000000..3309d37 --- /dev/null +++ b/doc/schema/inclusion_proof.schema.json @@ -0,0 +1,30 @@ +{ + "$schema": "https://json-schema.org/draft-07/schema#", + "title": "inclusion_proof", + "description": "JSON-formatted inclusion proof, version 0.", + + "type": "object", + "required": [ "tree_size", "leaf_index", "inclusion_proof" ], + "properties": { + "tree_size": { + "description": "The Merkle tree size that the inclusion proof is based on.", + "type": "integer", + "minimum": 0 + }, + "leaf_index": { + "description": "The zero-based index of the leaf that the inclusion proof is for.", + "type": "integer", + "minimum": 0 + }, + "inclusion_proof": { + "description": "A list of base64-encoded node hashes that proves inclusion", + "type": "array", + "items": { + "description": "A node hash in base64", + "type": "string", + "minLength": 44, + "maxLength": 44 + } + } + } +} diff --git a/doc/schema/leaves.schema.json b/doc/schema/leaves.schema.json new file mode 100644 index 0000000..74d7454 --- /dev/null +++ b/doc/schema/leaves.schema.json @@ -0,0 +1,38 @@ +{ + "$schema": "https://json-schema.org/draft-07/schema#", + "title": "list of tree_leaf", + "description": "JSON-formatted tree leaf list, version 0.", + + "type": "array", + "description": "A list Merkle tree leaves", + "items": { + "type": "object", + "required": [ "checksum", "signature_scheme", "signature", "key_hash" ], + "properties": { + "checksum": { + "description": "A cryptographic hash that is computed over some data of opaque type. The result is base64-encoded.", + "type": "string", + "minLength": 44, + "maxLength": 44 + }, + "signature_scheme": { + "description": "An integer that identifies the signature scheme used by the submitter. See API documentation.", + "type": "integer", + "enum": [ 1, 2, 3 ] + }, + "signature": { + "description": "The submitter's signature over the checksum in base64", + "type": "string", + "minLength": 44, + "maxLength": 684 + }, + "key_hash": { + "description": "A public verification-key hash that identifies the signer.", + "type": "string", + "minLength": 44, + "maxLength": 44 + } + } + }, + "minItems": 1 +} diff --git a/doc/schema/sth.schema.json b/doc/schema/sth.schema.json new file mode 100644 index 0000000..86de2d3 --- /dev/null +++ b/doc/schema/sth.schema.json @@ -0,0 +1,50 @@ +{ + "$schema": "https://json-schema.org/draft-07/schema#", + "title": "signed_tree_head_v0", + "description": "JSON-formatted signed tree head, version 0.", + + "type": "object", + "required": [ "timestamp", "tree_size", "root_hash", "signatures" ], + "properties": { + "timestamp": { + "description": "The number of milliseconds since the UNIX epoch (January 1, 1970 00:00:00 UTC).", + "type": "integer", + "minimum": 0 + }, + "tree_size": { + "description": "The number of entries that are stored in the log's Merkle tree.", + "type": "integer", + "minimum": 0 + }, + "root_hash": { + "description": "The log's Merkle tree root hash in base64.", + "type": "string", + "minLength": 44, + "maxLength": 44 + }, + "signatures": { + "description": "A list of signer-signature pairs.", + "type": "array", + "items": { + "description": "A signer-signature pair.", + "type": "object", + "required": [ "key_hash", "signature" ], + "properties": { + "key_hash": { + "description": "A public verification-key hash that identifies the signer in base64.", + "type": "string", + "minLength": 44, + "maxLength": 44 + }, + "signature": { + "description": "The signer's signature over the log's tree_leaf structure in base64.", + "type": "string", + "minLength": 44, + "maxLength": 44 + } + } + }, + "minItems": 1 + } + } +} diff --git a/doc/sketch.md b/doc/sketch.md deleted file mode 100644 index 31964e0..0000000 --- a/doc/sketch.md +++ /dev/null @@ -1,372 +0,0 @@ -# System Transparency Logging -This document provides a sketch of System Transparency (ST) logging. The basic -idea is to insert hashes of system artifacts into a public, append-only, and -tamper-evident transparency log, such that any enforcing client can be sure that -they see the same system artifacts as everyone else. A system artifact could -be a browser update, an operating system image, a Debian package, or more -generally something that is opaque. - -We take inspiration from the Certificate Transparency Front-End -([CTFE](https://github.com/google/certificate-transparency-go/tree/master/trillian/ctfe)) -that implements [RFC 6962](https://tools.ietf.org/html/rfc6962) for -[Trillian](https://transparency.dev). - -## Log parameters -An ST log is defined by the following parameters: -- `log_identifier`: a `Namespace` of type `ed25519_v1` that defines the log's -signing algorithm and public verification key. -- `supported_namespaces`: a list of namespace types that the log supports. -Entities must use a supported namespace type when posting signed data to the -log. -- `base_url`: prefix used by clients that contact the log, e.g., -example.com:1234/log. -- `final_cosigned_tree_head`: an `StItem` of type `cosigned_tree_head_v*`. Not -set until the log is turned into read-only mode in preparation of a shutdown. - -ST logs use the same hash strategy as described in RFC 6962: SHA256 with `0x00` -as leaf node prefix and `0x01` as interior node prefix. - -In contrast to Certificate Transparency (CT) **there is no Maximum Merge Delay -(MMD)**. New entries are merged into the log as soon as possible, and no client -should trust that something is logged until an inclusion proof can be provided -that references a trustworthy STH. Therefore, **there are no "promises" of -public logging** as in CT. - -To produce trustworthy STHs a simple form of [witness -cosigning](https://arxiv.org/pdf/1503.08768.pdf) is built into the log. -Witnesses poll the log for the next stable STH, and verify that it is consistent -before posting a cosignature that can then be served by the log. - -## Acceptance criteria and scope -A log should accept a leaf submission if it is: -- Well-formed, see data structure definitions below. -- Digitally signed by a registered namespace. - -Rate limits may be applied per namespace to combat spam. Namespaces may also be -used by clients to determine which entries belong to who. It is up to the -submitters to communicate trusted namespaces to their own clients. In other -words, there are no mappings from namespaces to identities built into the log. -There is also no revocation of namespaces: **we facilitate _detection_ of -compromised signing keys by making artifact hashes public, which is not to be -confused with _prevention_ or even _recovery_ after detection**. - -## Data structure definitions -Data structures are defined and serialized using the presentation language in -[RFC 5246, §4](https://tools.ietf.org/html/rfc5246). A definition of the log's -Merkle tree can be found in [RFC 6962, -§2](https://tools.ietf.org/html/rfc6962#section-2). - -### Namespace -A _namespace_ is a versioned data structure that contains a public verification -key (or fingerprint), as well as enough information to determine its format, -signing, and verification operations. Namespaces are used as identifiers, both -for the log itself and the parties that submit artifact hashes and cosignatures. - -``` -enum { - reserved(0), - ed25519_v1(1), - (2^16-1) -} NamespaceFormat; - -struct { - NamespaceFormat format; - select (format) { - case ed25519_v1: Ed25519V1; - } message; -} Namespace; -``` - -Our namespace format is inspired by Keybase's -[key-id](https://keybase.io/docs/api/1.0/kid). - -#### Ed25519V1 -At this time the only supported namespace type is based on Ed25519. The -namespace field contains the full verification key. Signing operations and -serialized formats are defined by [RFC -8032](https://tools.ietf.org/html/rfc8032). -``` -struct { - opaque namespace[32]; // public verification key -} Ed25519V1; -``` - -### `StItem` -A general-purpose `TransItem` is defined in [RFC 6962/bis, -§4.5](https://tools.ietf.org/html/draft-ietf-trans-rfc6962-bis-34#section-4.5). -We define our own `TransItem`, but name it `StItem` to emphasize that they are -not the same. - -``` -enum { - reserved(0), - signed_tree_head_v1(1), - cosigned_tree_head_v1(2), - consistency_proof_v1(3), - inclusion_proof_v1(4), - signed_checksum_v1(5), // leaf type - (2^16-1) -} StFormat; - -struct { - StFormat format; - select (format) { - case signed_tree_head_v1: SignedTreeHeadV1; - case cosigned_tree_head_v1: CosignedTreeHeadV1; - case consistency_proof_v1: ConsistencyProofV1; - case inclusion_proof_v1: InclusionProofV1; - case signed_checksum_v1: SignedChecksumV1; - } message; -} StItem; - -struct { - StItem items<0..2^32-1>; -} StItemList; -``` - -#### `signed_tree_head_v1` -We use the same tree head definition as in [RFC 6962/bis, -§4.9](https://tools.ietf.org/html/draft-ietf-trans-rfc6962-bis-34#section-4.9). -The resulting _signed_ tree head is packaged differently: a namespace is used as -log identifier, and it is communicated in a `SignatureV1` structure. -``` -struct { - TreeHeadV1 tree_head; - SignatureV1 signature; -} SignedTreeHeadV1; - -struct { - uint64 timestamp; - uint64 tree_size; - NodeHash root_hash; - Extension extensions<0..2^16-1>; -} TreeHeadV1; -opaque NodeHash<32..2^8-1>; - -struct { - Namespace namespace; - opaque signature<1..2^16-1>; -} SignatureV1; -``` - -#### `cosigned_tree_head_v1` -Transparency logs were designed to be cryptographically verifiable in the -presence of a gossip-audit model that ensures everyone observes _the same -cryptographically verifiable log_. The gossip-audit model is largely undefined -in today's existing transparency logging ecosystems, which means that the logs -must be trusted to play by the rules. We wanted to avoid that outcome in our -ecosystem. Therefore, a gossip-audit model is built into the log. - -The basic idea is that an STH should only be considered valid if it is cosigned -by a number of witnesses that verify the append-only property. Which witnesses -to trust and under what circumstances is defined by a client-side _witness -cosigning policy_. For example, - "require no witness cosigning", - "must have at least `k` signatures from witnesses A...J", and - "must have at least `k` signatures from witnesses A...J where one is from - witness B". - -Witness cosigning policies are beyond the scope of this specification. - -A cosigned STH is composed of an STH and a list of cosignatures. A cosignature -must cover the serialized STH as an `StItem`, and be produced with a witness -namespace of type `ed25519_v1`. - -``` -struct { - SignedTreeHeadV1 signed_tree_head; - SignatureV1 cosignatures<0..2^32-1>; // vector of cosignatures -} CosignedTreeHeadV1; -``` - -#### `consistency_proof_v1` -For the most part we use the same consistency proof definition as in [RFC -6962/bis, -§4.11](https://tools.ietf.org/html/draft-ietf-trans-rfc6962-bis-34#section-4.11). -There are two modifications: our log identifier is a namespace rather than an -[OID](https://tools.ietf.org/html/draft-ietf-trans-rfc6962-bis-34#section-4.4), -and a consistency proof may be empty. - -``` -struct { - Namespace log_id; - uint64 tree_size_1; - uint64 tree_size_2; - NodeHash consistency_path<0..2^16-1>; -} ConsistencyProofV1; -``` - -#### `inclusion_proof_v1` -For the most part we use the same inclusion proof definition as in [RFC -6962/bis, -§4.12](https://tools.ietf.org/html/draft-ietf-trans-rfc6962-bis-34#section-4.12). -There are two modifications: our log identifier is a namespace rather than an -[OID](https://tools.ietf.org/html/draft-ietf-trans-rfc6962-bis-34#section-4.4), -and an inclusion proof may be empty. -``` -struct { - Namespace log_id; - uint64 tree_size; - uint64 leaf_index; - NodeHash inclusion_path<0..2^16-1>; -} InclusionProofV1; -``` - -#### `signed_checksum_v1` -A checksum entry contains a package identifier like `foobar-1.2.3` and an -artifact hash. It is then signed so that clients can distinguish artifact -hashes from two different software publishers A and B. For example, the -`signed_checksum_v1` type can help [enforce public binary logging before -accepting a new software -update](https://wiki.mozilla.org/Security/Binary_Transparency). - -``` -struct { - ChecksumV1 data; - SignatureV1 signature; -} SignedChecksumV1; - -struct { - opaque identifier<1..128>; - opaque checksum<1..64>; -} ChecksumV1; -``` - -It is assumed that clients know how to find the real artifact source (if not -already at hand), such that the logged hash can be recomputed and compared for -equality. The log is not aware of how artifact hashes are computed, which means -that it is up to the submitters to define hash functions, data formats, and -such. - -## Public endpoints -Clients talk to the log using HTTP(S). Successfully processed requests are -responded to with HTTP status code `200 OK`, and any returned data is -serialized. Endpoints without input parameters use HTTP GET requests. -Endpoints that have input parameters HTTP POST a TLS-serialized data structure. -The HTTP content type `application/octet-stream` is used when sending data. - -### add-entry -``` -POST https:///st/v1/add-entry -``` - -Input: -- An `StItem` of type `signed_checksum_v1`. - -No output. - -### add-cosignature -``` -POST https:///st/v1/add-cosignature -``` - -Input: -- An `StItem` of type `cosigned_tree_head_v1`. The list of cosignatures must -be of length one, the witness signature must cover the item's STH, and that STH -must additionally match the log's stable STH that is currently being cosigned. - -No output. - -### get-latest-sth -``` -GET https:///st/v1/get-latest-sth -``` - -No input. - -Output: -- An `StItem` of type `signed_tree_head_v1` that corresponds to the most -recent STH. - -### get-stable-sth -``` -GET https:///st/v1/get-stable-sth -``` - -No input. - -Output: -- An `StItem` of type `signed_tree_head_v1` that corresponds to a stable STH -that witnesses should cosign. The same STH is returned for a period of time. - -### get-cosigned-sth -``` -GET https:///st/v1/get-cosigned-sth -``` - -No input. - -Output: -- An `StItem` of type `cosigned_tree_head_v1` that corresponds to the most -recent cosigned STH. - -### get-proof-by-hash -``` -POST https:///st/v1/get-proof-by-hash -``` - -Input: -``` -struct { - opaque hash[32]; // leaf hash - uint64 tree_size; // tree size that the proof should be based on -} GetProofByHashV1; -``` - -Output: -- An `StItem` of type `inclusion_proof_v1`. - -### get-consistency-proof -``` -POST https:///st/v1/get-consistency-proof -``` - -Input: -``` -struct { - uint64 first; // first tree size that the proof should be based on - uint64 second; // second tree size that the proof should be based on -} GetConsistencyProofV1; -``` - -Output: -- An `StItem` of type `consistency_proof_v1`. - -### get-entries -``` -POST https:///st/v1/get-entries -``` - -Input: -``` -struct { - uint64 start; // 0-based index of first entry to retrieve - uint64 end; // 0-based index of last entry to retrieve in decimal. -} GetEntriesV1; -``` - -Output: -- An `StItem` list where each entry is of type `signed_checksum_v1`. The first -`StItem` corresponds to the start index, the second one to `start+1`, etc. The -log may return fewer entries than requested. - -# Appendix A -In the future other namespace types might be supported. For example, we could -add [RSASSA-PKCS1-v1_5](https://tools.ietf.org/html/rfc3447#section-8.2) as -follows: -1. Add `rsa_v1` format and RSAV1 namespace. This is what we would register on -the server-side such that the server knows the namespace and complete key. -``` -struct { - opaque namespace<32>; // key fingerprint - // + some encoding of public key -} RSAV1; -``` -2. Add `rsassa_pkcs1_5_v1` format and `RSASSAPKCS1_5_v1`. This is what the -submitter would use to communicate namespace and RSA signature mode. -``` -struct { - opaque namespace<32>; // key fingerprint - // + necessary parameters, e.g., SHA256 as hash function -} RSASSAPKCS1_5V1; -``` -- cgit v1.2.3 From aa8f64c0ed18f384a6af1ade6268b35ec60dac85 Mon Sep 17 00:00:00 2001 From: Rasmus Dahlberg Date: Tue, 20 Apr 2021 21:45:03 +0200 Subject: added shard_hint --- doc/api.md | 34 ++++++++++++++++++++++++++++++++++ 1 file changed, 34 insertions(+) diff --git a/doc/api.md b/doc/api.md index 760663b..0f873e4 100644 --- a/doc/api.md +++ b/doc/api.md @@ -119,6 +119,39 @@ A key-hash is included in the leaf so that it can be attributed to the signing entity. A hash, rather than the full public verification key, is used to force the verifier to locate the appropriate key and make an explicit trust decision. +#### Shard hint +The log is only accepting new leaves during a predefined time interval. We +refer to this time interval as the log's _shard_. Sharding can simplify log +operations because it becomes explicit when the log can be shutdown. + +Unlike X.509 certificates that already have a validity range, a checksum does +not have any such information. Therefore, we require the submitter to sign a +_shard hint_. A shard hint is composed of a prefix and a tree leaf. + +``` +struct shard_hint { + u64 prefix; + struct tree_leaf leaf; +} +``` + +The log will check that the signed `shard_hint` can be verified using the +submitter's public verification key. The prefix could be anything and may +repeat. This API documentation assumes that the prefix is set to zero. + +As long as the `shard_hint` signature is not revealed, no one but the submitter +can submit a leaf that the log will accept. Therefore, the good Samaritan +cannot submit all leaves from an earlier shard into a newer one. The +`shard_hint` does not prevent the _legitimate submitter_ from reusing an earlier +submission in a future shard. + +Note the importance of letting the submitter decide if an entry is logged again +or not. If the log has a rate limiting function, replayed submissions could +deny service in a new shard. In practise we expect submitters to not log a +leaf again. Once an inclusion proof and a cosigned tree head is available, you +have all the necessary proofs. These proofs continue to be valid after the log +shuts down because the verification process is non-interactive. + ## Public endpoints Every log has a base URL that identifies it uniquely. The only constraint is that it must be a valid HTTP(S) URL that can have the `/st/v0/` suffix @@ -199,6 +232,7 @@ Input key-value pairs: - `leaf_checksum`: the checksum that the submitter wants to log in base64. - `signature_scheme`: the signature scheme that the submitter wants to use. - `tree_leaf_signature`: the submitter's `tree_leaf` signature in base64. +- `shard_hint_signature`: the submitter's `shard_hint` signature in base64. - `verification_key`: the submitter's public verification key. It is serialized as described in the corresponding RFC, then base64-encoded. - `domain_hint`: a domain name that indicates where the public verification-key -- cgit v1.2.3 From 66b3d09b526c3c1e8d5f2d9a92deba497ca8124c Mon Sep 17 00:00:00 2001 From: Rasmus Dahlberg Date: Mon, 26 Apr 2021 12:48:22 +0200 Subject: moved shard_hint into tree_leaf --- doc/api.md | 114 ++++++++++++++++++++++++++++++++++--------------------------- 1 file changed, 63 insertions(+), 51 deletions(-) diff --git a/doc/api.md b/doc/api.md index 0f873e4..b5d54e6 100644 --- a/doc/api.md +++ b/doc/api.md @@ -85,72 +85,74 @@ struct tree_head { }; ``` -The serialized tree head must be signed using Ed25519. A witness must only sign -the log's tree head if it is consistent with prior history and the timestamp is -roughly correct. A timestamp is roughly correct if it is not backdated or -future-dated more than 12 hours. +The serialized tree head must be signed using Ed25519. A witness must not +cosign a tree head if it is inconsistent with prior history or if the timestamp +is backdated or future-dated more than 12 hours. #### Merkle tree leaf -The log supports a single leaf type. It contains a checksum, a signature -scheme, a signature that the submitter computed over that checksum, and the hash -of the public verification key that can be used to verify the signature. +The log supports a single leaf type. It contains a message, a signature scheme, +a signature that the submitter computed over the message, and a hash of the +public verification key that can be used to verify the signature. ``` -const ALG_ED25519 = 1; // RFC 8032 -const ALG_RSASSA_PKCS1_V1_5 = 2; // RFC 8017 -const ALG_RSASSA_PSS = 3; // RFC 8017 +const SIGNATURE_SCHEME_ED25519 = 1; // RFC 8032 +const SIGNATURE_SCHEME_RSASSA_PKCS1_V1_5 = 2; // RFC 8017 +const SIGNATURE_SCHEME_RSASSA_PSS = 3; // RFC 8017 -struct tree_leaf { +struct signature_ed25519 { + u8 signature[32]; +}; + +struct signature_rsassa { + u64 num_bytes IN [ 256, 384, 512 ]; + u8 signature[num_bytes]; +}; + +struct message { + u64 shard_hint; u8 checksum[32]; +}; + +struct tree_leaf { + struct message message; u64 signature_scheme IN [ - ALG_ED25519, - ALG_RSASSA_PKCS1_V1_5, - ALG_RSASSA_PSS, + SIGNATURE_SCHEME_ED25519, + SIGNATURE_SCHEME_RSASSA_PKCS1_V1_5, + SIGNATURE_SCHEME_RSASSA_PSS, ]; union signature[signature_scheme] { - ALG_ED25519: u8 ed25519[32]; - default: u8 rsa[512]; + SIGNATURE_SCHEME_ED25519: struct signature_ed25519 ed25519; + default: struct signature_rsassa rsassa; } u8 key_hash[32]; } ``` -A key-hash is included in the leaf so that it can be attributed to the signing -entity. A hash, rather than the full public verification key, is used to force -the verifier to locate the appropriate key and make an explicit trust decision. - -#### Shard hint -The log is only accepting new leaves during a predefined time interval. We -refer to this time interval as the log's _shard_. Sharding can simplify log -operations because it becomes explicit when the log can be shutdown. - -Unlike X.509 certificates that already have a validity range, a checksum does -not have any such information. Therefore, we require the submitter to sign a -_shard hint_. A shard hint is composed of a prefix and a tree leaf. +Unlike X.509 certificates that already have validity ranges, a checksum does not +have any such information. Therefore, we require that the submitter selects a +_shard hint_. The selected shard hint must be in the log's _shard interval_. A +shard interval is defined by a start time and an end time. Both ends of the +shard interval are inclusive and expressed as the number of milliseconds since +the UNIX epoch (January 1, 1970 00:00:00 UTC). -``` -struct shard_hint { - u64 prefix; - struct tree_leaf leaf; -} -``` +Sharding simplifies log operations because it becomes explicit when a log can be +shutdown. A log must only accept logging requests that have valid shard hints. +A log should only accept logging requests during the predefined shard interval. +Note that _the submitter's shard hint is not a verified timestamp_. The +submitter should set the shard hint as large as possible. If a roughly verified +timestamp is needed, a cosigned tree head can be used. -The log will check that the signed `shard_hint` can be verified using the -submitter's public verification key. The prefix could be anything and may -repeat. This API documentation assumes that the prefix is set to zero. +Without a shard hint, the good Samaritan could log all leaves from an earlier +shard into a newer one. Not only would that defeat the purpose of sharding, but +it would also become a potential denial-of-service vector. -As long as the `shard_hint` signature is not revealed, no one but the submitter -can submit a leaf that the log will accept. Therefore, the good Samaritan -cannot submit all leaves from an earlier shard into a newer one. The -`shard_hint` does not prevent the _legitimate submitter_ from reusing an earlier -submission in a future shard. +The signed message is composed of the selected shard hint and the submitter's +checksum. It must be possible to verify the signature using the specified +signature scheme and the submitter's public verification key. -Note the importance of letting the submitter decide if an entry is logged again -or not. If the log has a rate limiting function, replayed submissions could -deny service in a new shard. In practise we expect submitters to not log a -leaf again. Once an inclusion proof and a cosigned tree head is available, you -have all the necessary proofs. These proofs continue to be valid after the log -shuts down because the verification process is non-interactive. +A key-hash is included in the leaf so that it can be attributed to the signing +entity. A hash, rather than the full public verification key, is used to force +the verifier to locate the appropriate key and make an explicit trust decision. ## Public endpoints Every log has a base URL that identifies it uniquely. The only constraint is @@ -229,10 +231,10 @@ POST /st/v0/add-leaf ``` Input key-value pairs: -- `leaf_checksum`: the checksum that the submitter wants to log in base64. +- `shard_hint`: the shard hint that the submitter selected. +- `checksum`: the checksum that the submitter wants to log in base64. - `signature_scheme`: the signature scheme that the submitter wants to use. -- `tree_leaf_signature`: the submitter's `tree_leaf` signature in base64. -- `shard_hint_signature`: the submitter's `shard_hint` signature in base64. +- `signature`: the submitter's signature over `tree_leaf.message` in base64. - `verification_key`: the submitter's public verification key. It is serialized as described in the corresponding RFC, then base64-encoded. - `domain_hint`: a domain name that indicates where the public verification-key @@ -279,3 +281,13 @@ human-readable error message. The key-hash can be used to identify which witness signed the log's tree head. A key-hash, rather than the full verification key, is used to force the verifier to locate the appropriate key and make an explicit trust decision. + +## Summary of log parameters +- **Public key**: an Ed25519 verification key that can be used to verify the +log's tree head signatures. +- **Log identifier**: the hashed public verification key using SHA256. +- **Shard interval**: the time during which the log accepts logging requests. +The shard interval's start and end are inclusive and expressed as the number of +milliseconds since the UNIX epoch. +- **Base URL**: where the log can be reached over HTTP(S). It is the prefix +before a version-0 specific endpoint. -- cgit v1.2.3 From 83d38bfc5c3b9304953d04a4679658e3c2645367 Mon Sep 17 00:00:00 2001 From: Rasmus Dahlberg Date: Mon, 26 Apr 2021 15:12:57 +0200 Subject: drafty experiment where we would only use percent encoding --- doc/api.md | 206 ++++++++++++++++++++++++++++++++++--------------------------- 1 file changed, 116 insertions(+), 90 deletions(-) diff --git a/doc/api.md b/doc/api.md index b5d54e6..174d2c9 100644 --- a/doc/api.md +++ b/doc/api.md @@ -8,28 +8,18 @@ This is a work-in-progress document that may be moved or modified. ## Overview The log implements an HTTP(S) API: -- Requests that add data to the log use the HTTP POST method. The HTTP content -type is `application/x-www-form-urlencoded`. The posted data are key-value -pairs. Binary data must be base64-encoded. -- Requests that retrieve data from the log use the HTTP GET method. The HTTP -content type is `application/x-www-form-urlencoded`. Input parameters are -key-value pairs. -- Responses are JSON objects. The HTTP content type is `application/json`. -- Error messages are human-readable strings. The HTTP content type is -`text/plain`. - -We decided to use these web formats for requests and responses because the log -is running as an HTTP(S) service. In other words, anyone that interacts with -the log is most likely using these formats already. The other benefit is that -all requests and responses are human-readable. This makes it easier to -understand the protocol, troubleshoot issues, and copy-paste. We favored -compatibility and understandability over a wire-efficient format. - -Note that we are not using JSON for signed and/or logged data. In other words, -a submitter that wishes to distribute log responses to their user base in a -different format may do so. The forced (de)serialization parser on _end-users_ -is a small subset of Trunnel. Trunnel is an "idiot-proof" wire-format that the -Tor project uses. +- Requests that add data to the log use the HTTP POST method. +- Request that retrieve data from the log use the HTTP GET method. +- The HTTP content type is `application/x-www-form-urlencoded` for requests and +responses. This means that all input and output are expressed as key-value +pairs. Binary data must be hex-encoded. + +We decided to use percent encoding for requests and responses because it is a +_simple format_ that is commonly used on the web. We are not using percent +encoding for signed and/or logged data. In other words, a submitter may +distribute log responses to their end-users in a different format that suit +them. The forced (de)serialization parser on _end-users_ is a small subset of +Trunnel. Trunnel is an "idiot-proof" wire-format that the Tor project uses. ## Primitives ### Cryptography @@ -49,6 +39,13 @@ padding. Supporting RSA is suboptimal, but excluding it would make the log useless for many possible adopters. ### Serialization +Log requests and responses are percent encoded. Percent encoding is a smaller +dependency than an alternative parser like JSON. It is comparable to rolling +your own minimalistic line-terminated format. Some input and output data is +binary: cryptographic hashes and signatures. Binary data must be expressed as +hex before percent-encoding it. We decided to use hex as opposed to base64 +because it is simpler, favoring simplicity over efficiency on the wire. + We use the [Trunnel](https://gitweb.torproject.org/trunnel.git) [description language](https://www.seul.org/~nickm/trunnel-manual.html) to define (de)serialization of data structures that need to be signed or inserted into the Merkle tree. Trunnel is more expressive than the @@ -62,13 +59,12 @@ A fair summary of our Trunnel usage is as follows. All integers are 64-bit, unsigned, and in network byte order. A fixed-size byte array is put into the serialization buffer in-order, starting from the first -byte. These basic types are concatenated to form a collection. You should not -need a general-purpose Trunnel (de)serialization parser to work with this -format. If you have one, you may use it though. The main point of using -Trunnel is that it makes a simple format explicit and unambiguous. - -TODO: URL-encode _or_ JSON? I think we should only need one. Always doing HTTP -POST would also ensure that input parameters don't show up in web server logs. +byte. A variable length byte array first declares its length as an integer, +which is then followed by that number of bytes. These basic types are +concatenated to form a collection. You should not need a general-purpose +Trunnel (de)serialization parser to work with this format. If you have one, you +may use it though. The main point of using Trunnel is that it makes a simple +format explicit and unambiguous. #### Merkle tree head Tree heads are signed by the log and its witnesses. It contains a timestamp, a @@ -160,91 +156,124 @@ that it must be a valid HTTP(S) URL that can have the `/st/v0/` suffix appended. For example, a complete endpoint URL could be `https://log.example.com/2021/st/v0/get-signed-tree-head`. +The HTTP status code is 200 OK to indicate success. A different HTTP status +code is used to indicate failure. The log should set the "error" key to a +human-readable value that describes what went wrong. For example, +`error=invalid+signature`, `error=rate+limit+exceeded`, or +`error=unknown+leaf+hash`. + ### get-signed-tree-head ``` GET /st/v0/get-signed-tree-head ``` -Input key-value pairs: -- `type`: either the string "latest", "stable", or "cosigned". - - "latest": ask for the most recent signed tree head. - - "stable": ask for a recent signed tree head that is fixed for some period +Input: +- "type": either the string "latest", "stable", or "cosigned". + - latest: ask for the most recent signed tree head. + - stable: ask for a recent signed tree head that is fixed for some period of time. - - "cosigned": ask for a recent cosigned tree head. - -Output: -- On success: status 200 OK and a signed tree head. The response body is -defined by the following [schema](https://github.com/system-transparency/stfe/blob/design/doc/schema/sth.schema.json). -- On failure: a different status code and a human-readable error message. + - cosigned: ask for a recent cosigned tree head. + +Output on success: +- "timestamp": `tree_head.timestamp` as a human-readable number. +- "tree_size": `tree_head.tree_size` as a human-readable number. +- "root_hash": `tree_head.root_hash` in hex. +- "signature": an Ed25519 signature over `tree_head`. The result is +hex-encoded. +- "key_hash": a hash of the public verification key that can be used to verify +the signature. The public verification key is serialized as in RFC 8032, then +hashed using SHA256. The result is hex-encoded. + +The "signature" and "key_hash" fields may repeat. The first signature +corresponds to the first key hash, the second signature corresponds to the +second key hash, etc. The number of signatures and key hashes must match. ### get-proof-by-hash ``` POST /st/v0/get-proof-by-hash ``` -Input key-value pairs: -- `leaf_hash`: a base64-encoded leaf hash that identifies which `tree_leaf` the +Input: +- "leaf_hash": a hex-encoded leaf hash that identifies which `tree_leaf` the log should prove inclusion for. The leaf hash is computed using the RFC 6962 -hashing strategy. In other words, `H(0x00 | tree_leaf)`. -- `tree_size`: the tree size of a tree head that the proof should be based on. +hashing strategy. In other words, `SHA256(0x00 | tree_leaf)`. +- "tree_size": a human-readable tree size of the tree head that the proof should +be based on. -Output: -- On success: status 200 OK and an inclusion proof. The response body is -defined by the following [schema](https://github.com/system-transparency/stfe/blob/design/doc/schema/inclusion_proof.schema.json). -- On failure: a different status code and a human-readable error message. +Output on success: +- "tree_size": human-readable tree size that the proof is based on. +- "leaf_index": human-readable zero-based index of the leaf that the proof is +based on. +- "inclusion_path": a node hash in hex. + +The "inclusion_path" may be omitted or repeated to represent an inclusion proof +of zero or more node hashes. The order of node hashes follow from our hash +strategy, see RFC 6962. ### get-consistency-proof ``` POST /st/v0/get-consistency-proof ``` -Input key-value pairs: -- `new_size`: the tree size of a newer tree head. -- `old_size`: the tree size of an older tree head that the log should prove is -consistent with the newer tree head. +Input: +- "new_size": human-readable tree size of a newer tree head. +- "old_size": human-readable tree size of an older tree head that the log should +prove is consistent with the newer tree head. + +Output on success: +- "new_size": human-readable tree size of a newer tree head that the proof +is based on. +- "old_size": human-readable tree size of an older tree head that the proof is +based on. +- "consistency_path": a node hash in hex. -Output: -- On success: status 200 OK and a consistency proof. The response body is -defined by the following [schema](https://github.com/system-transparency/stfe/blob/design/doc/schema/consistency_proof.schema.json). -- On failure: a different status code and a human-readable error message. +The "consistency_path" may be omitted or repeated to represent a consistency +proof of zero or more node hashes. The order of node hashes follow from our +hash strategy, see RFC 6962. ### get-leaves ``` POST /st/v0/get-leaves ``` -Input key-value pairs: -- `start_size`: zero-based index of the first leaf to retrieve. -- `end_size`: index of the last leaf to retrieve. +Input: +- "start_size": human-readable index of the first leaf to retrieve. +- "end_size": human-readable index of the last leaf to retrieve. -Output: -- On success: status 200 OK and a list of leaves. The response body is -defined by the following [schema](https://github.com/system-transparency/stfe/blob/design/doc/schema/leaves.schema.json). -- On failure: a different status code and a human-readable error message. +Output on success: +- "shard_hint": `tree_leaf.message.shard_hint` as a human-readable number. +- "checksum": `tree_leaf.message.checksum` in hex. +- "signature_scheme": human-readable number that identifies a signature scheme. +- "signature": `tree_leaf.signature` in hex. +- "key_hash": `tree_leaf.key_hash` in hex. -The log may truncate the list of returned leaves. However, it must not be an -empty list on success. +All fields may be repeated to return more than one leaf. The first value in +each list refers to the first leaf, the second value in each list refers to the +second leaf, etc. The size of each list must match. + +The log may return fewer leaves than requested. At least one leaf must be +returned on HTTP status code 200 OK. ### add-leaf ``` POST /st/v0/add-leaf ``` -Input key-value pairs: -- `shard_hint`: the shard hint that the submitter selected. -- `checksum`: the checksum that the submitter wants to log in base64. -- `signature_scheme`: the signature scheme that the submitter wants to use. -- `signature`: the submitter's signature over `tree_leaf.message` in base64. -- `verification_key`: the submitter's public verification key. It is serialized -as described in the corresponding RFC, then base64-encoded. -- `domain_hint`: a domain name that indicates where the public verification-key -hash can be downloaded in base64. Supported methods: DNS and HTTPS -(TODO: docdoc). - -Output: -- On success: HTTP 200. The log will _try_ to incorporate the submitted leaf -into its Merkle tree. -- On failure: a different status code and a human-readable error message. +Input: +- "shard_hint": human-readable number in the log's shard interval that the +submitter selected. +- "checksum": the cryptographic checksum that the submitter wants to log in hex. +- "signature_scheme": human-readable number that identifies the submitter's +signature scheme. +- "signature": the submitter's signature over `tree_leaf.message`. The result +is hex-encoded. +- "verification_key": the submitter's public verification key. It is serialized +as described in the corresponding RFC. The result is hex-encoded. +- "domain_hint": a domain name that indicates where `tree_leaf.key_hash` can be +retrieved as a DNS TXT resource record in hex. + +Output on success: +- None The submitted entry will not be accepted if the signature is invalid or if the downloaded verification-key hash does not match. The submitted entry may also @@ -260,23 +289,20 @@ Public logging should not be assumed until an inclusion proof is available. An inclusion proof should not be relied upon unless it leads up to a trustworthy signed tree head. Witness cosigning can make a tree head trustworthy. -TODO: the log may allow no `domain_hint`? Especially useful for v0 testing. - ### add-cosignature ``` POST /st/v0/add-cosignature ``` -Input key-value pairs: -- `signature`: a base64-encoded signature over a `tree_head` that is fixed for -some period of time. The cosigning witness retrieves the tree head using the -`get-signed-tree-head` endpoint with the "stable" type. -- `key_hash`: a base64-encoded hash of the public verification key that can be -used to verify the signature. +Input: +- "signature": an Ed25519 signature over `tree_head`. The result is +hex-encoded. +- "key_hash": a hash of the public verification key that can be used to verify +the signature. The public verification key is serialized as in RFC 8032, then +hashed using SHA256. The result is hex-encoded. -Output: -- HTTP status 200 OK on success. Otherwise a different status code and a -human-readable error message. +Output on success: +- None The key-hash can be used to identify which witness signed the log's tree head. A key-hash, rather than the full verification key, is used to force the verifier -- cgit v1.2.3 From 87a2fa506c1861158ca04fd34d64e10b6447d8f3 Mon Sep 17 00:00:00 2001 From: Rasmus Dahlberg Date: Mon, 26 Apr 2021 19:54:06 +0200 Subject: added drafty threat model text --- doc/design.md | 30 ++++++++++++++++++++++++++++++ 1 file changed, 30 insertions(+) diff --git a/doc/design.md b/doc/design.md index f966d03..59cd7c8 100644 --- a/doc/design.md +++ b/doc/design.md @@ -28,5 +28,35 @@ System Transparency logging makes signed checksums transparent. The goal is to _detect_ unwanted key-usage without making assumptions about the signed data. ## Threat model and (non-)goals +We consider a powerful attacker that gained control of a target's signing and +release infrastructure. This covers a weaker form of attacker that is able to +sign data and distribute it to a subset of isolated users. For example, this is +essentially what FBI requested from Apple in the San Bernardino case [\[FBI-Apple\]](https://www.eff.org/cases/apple-challenges-fbi-all-writs-act-order). +The fact that signing keys and related infrastructure components get +compromised should not be controversial [\[SolarWinds\]](https://www.zdnet.com/article/third-malware-strain-discovered-in-solarwinds-supply-chain-attack/). + +The attacker can also gain control of the transparency log's signing key and +infrastructure. This covers a weaker form of attacker that is able to sign log +data and distribute it to a subset of isolated users. For example, this could +have been the case when a remote code execution was found for a Certificate +Transparency Log [\[DigiCert\]](https://groups.google.com/a/chromium.org/g/ct-policy/c/aKNbZuJzwfM). + +Any attacker that is able to position itself to control these components will +likely be _risk-averse_. This is at minimum due to two factors. First, +detection would result in a significant loss of capability that is by no means +trivial to come by. Second, detection means that some part of the attacker's +malicious behavior will be disclosed publicly. + +Our goal is to facilitate _detection_ of compromised signing keys. Therefore, +we transparency log signed checksums. We assume that clients _fail closed_ if a +checksum does not appear in a public log. We also assume that the attacker +controls at most a threshold of independent parties to achieve our goal +("strength in numbers"). + +It is a non-goal to disclose the data that a signed checksum represents. For +example, the log cannot distinguish between a checksum that represents a tax +declaration, an ISO image, or a Debian package. This means that the type of +detection we support is _courser-grained_ when compared to Certificate +Transparency. ## Design -- cgit v1.2.3 From 94fea7a3c993686d26efbf7ca9b73d598222a272 Mon Sep 17 00:00:00 2001 From: Rasmus Dahlberg Date: Thu, 29 Apr 2021 14:50:49 +0200 Subject: added start on design document Work in progress. --- doc/design.md | 196 ++++++++++++++++++++++++++++++++++++++++++++++++++++------ 1 file changed, 176 insertions(+), 20 deletions(-) diff --git a/doc/design.md b/doc/design.md index 59cd7c8..9fcf4b6 100644 --- a/doc/design.md +++ b/doc/design.md @@ -2,9 +2,9 @@ We propose System Transparency logging. It is similar to Certificate Transparency, expect that cryptographically signed checksums are logged as opposed to X.509 certificates. Publicly logging signed checksums allow anyone -to discover which keys signed what. As such, malicious and unintended key-usage -can be _discovered_. We present our design and discuss how two possible -use-cases influenced it: binary transparency and reproducible builds. +to discover which keys produced what signatures. As such, malicious and +unintended key-usage can be _detected_. We present our design and conclude by +providing two use-cases: binary transparency and reproducible builds. **Target audience.** You are most likely interested in transparency logs or supply-chain security. @@ -12,20 +12,20 @@ You are most likely interested in transparency logs or supply-chain security. **Preliminaries.** You have basic understanding of cryptographic primitives like digital signatures, hash functions, and Merkle trees. You roughly know what problem -Certificate Transparency solves and how. You may never have heard the term -_gossip-audit model_, or know how it is related to trust assumptions and -detectability properties. +Certificate Transparency solves and how. **Warning.** -This is a work-in-progress document that may be moved or modified. +This is a work-in-progress document that may be moved or modified. A future +revision of this document will bump the version number to v1. Please let us +know if you have any feedback. ## Introduction Transparency logs make it possible to detect unwanted events. For example, are there any (mis-)issued TLS certificates [\[CT\]](https://tools.ietf.org/html/rfc6962), did you get a different Go module than everyone else [\[ChecksumDB\]](https://go.googlesource.com/proposal/+/master/design/25530-sumdb.md), or is someone running unexpected commands on your server [\[AuditLog\]](https://transparency.dev/application/reliably-log-all-actions-performed-on-your-servers/). -System Transparency logging makes signed checksums transparent. The goal is to -_detect_ unwanted key-usage without making assumptions about the signed data. +A System Transparency log makes signed checksums transparent. The overall goal +is to facilitate detection of unwanted key-usage. ## Threat model and (non-)goals We consider a powerful attacker that gained control of a target's signing and @@ -33,7 +33,7 @@ release infrastructure. This covers a weaker form of attacker that is able to sign data and distribute it to a subset of isolated users. For example, this is essentially what FBI requested from Apple in the San Bernardino case [\[FBI-Apple\]](https://www.eff.org/cases/apple-challenges-fbi-all-writs-act-order). The fact that signing keys and related infrastructure components get -compromised should not be controversial [\[SolarWinds\]](https://www.zdnet.com/article/third-malware-strain-discovered-in-solarwinds-supply-chain-attack/). +compromised should not be controversial these days [\[SolarWinds\]](https://www.zdnet.com/article/third-malware-strain-discovered-in-solarwinds-supply-chain-attack/). The attacker can also gain control of the transparency log's signing key and infrastructure. This covers a weaker form of attacker that is able to sign log @@ -47,16 +47,172 @@ detection would result in a significant loss of capability that is by no means trivial to come by. Second, detection means that some part of the attacker's malicious behavior will be disclosed publicly. -Our goal is to facilitate _detection_ of compromised signing keys. Therefore, -we transparency log signed checksums. We assume that clients _fail closed_ if a -checksum does not appear in a public log. We also assume that the attacker -controls at most a threshold of independent parties to achieve our goal -("strength in numbers"). +Our goal is to facilitate _detection_ of compromised signing keys. We consider +a signing key compromised if an end-user accepts an unwanted signature as valid. +The solution that we propose is that signed checksums are transparency logged. +For security we need a collision resistant hash function and an unforgeable +signature scheme. We also assume that at most a threshold of seemingly +independent parties are adversarial. -It is a non-goal to disclose the data that a signed checksum represents. For -example, the log cannot distinguish between a checksum that represents a tax -declaration, an ISO image, or a Debian package. This means that the type of -detection we support is _courser-grained_ when compared to Certificate -Transparency. +It is a non-goal to disclose the data that a checksum represents. For example, +the log cannot distinguish between a checksum that represents a tax declaration, +an ISO image, or a Debian package. This means that the type of detection we +support is more _course-grained_ when compared to Certificate Transparency. ## Design +We consider a data publisher that wants to digitally sign their data. The data +is of opaque type. We assume that end-users have a mechanism to locate the +relevant public verification keys. Data and signatures can also be retrieved +(in)directly from the data publisher. We make little assumptions about the +signature tooling. The ecosystem at large can continue to use `gpg`, `openssl`, +`ssh-keygen -Y`, `signify`, or something else. + +We _have to assume_ that additional tooling can be installed by end-users that +wish to enforce transparency logging. For example, none of the existing +signature tooling support verification of Merkle tree proofs. A side-effect of +our design is that this additional tooling makes no outbound connections. The +above data flows are thus preserved. + +### A bird's view +A central part of any transparency log is the data. The data is stored by the +leaves of an append-only Merkle tree. Our leaf structure contains four fields: +- **shard_hint**: a number that binds the leaf to a particular _shard interval_. +Sharding means that the log has a predefined time during which logging requests +will be accepted. Once elapsed, the log can be shutdown. +- **checksum**: a cryptographic hash of some opaque data. The log never +sees the opaque data; just the hash. +- **signature**: a digital signature that is computed by the data publisher over +the leaf's shard hint and checksum. +- **key_hash**: a cryptographic hash of the public verification key that can be +used to verify the leaf's signature. + +#### Step 1 - preparing a logging request +The data publisher selects a shard hint and a checksum that should be logged. +For example, the shard hint could be "logs that are active during 2021". The +checksum might be a hashed release file or something else. + +The data publisher signs the selected shard hint and checksum using their secret +signing key. Both the signed message and the signature is stored +in the leaf for anyone to verify. Including a shard hint in the signed message +ensures that the good Samaritan cannot change it to log all leaves from an +earlier shard into a newer one. + +The hashed public verification key is also stored in the leaf. This makes it +easy to attribute the leaf to the signing entity. For example, a data publisher +that monitors the log can look for leaves that match their own key hash(es). + +A hash, rather than the full public verification key, is used to force the +verifier to locate the key and trust it explicitly. Not disclosing the public +verification key in the leaf makes it more difficult to use an untrusted key _by +mistake_. + +#### Step 2 - submitting a logging request +The log implements an HTTP(S) API. Input and output is human-readable and uses +percent encoding. We decided to use percent encoding for requests and responses +because it is a simple format that is commonly used on the web. A more complex +parser like JSON is not needed if the exchanged data structures are basic +enough. + +The data publisher submits their shard hint, checksum, signature, and public +verification key as key-value pairs. The log will use the public verification +key to check that the signature is valid, then hash it to construct the leaf. + +The data publisher also submits a _domain hint_. The log will download a DNS +TXT resource record based on the provided domain name. The downloaded result +must match the public verification key hash. By verifying that the submitter +controls a domain that is aware of the public verification key, rate limits can +be applied per second-level domain. As a result, you would need a large number +of domain names to spam the log in any significant way. + +Using DNS to combat spam is convenient because many data publishers already have +a domain name. A single domain name is also relatively cheap. Another +benefit is that the same anti-spam mechanism can be used across several +independent logs without coordination. This is important because a healthy log +ecosystem needs more than one log to be reliable. DNS also has built-in +caching that can be influenced by setting TTLs accordingly. + +The submitter's domain hint is not part of the leaf because key management is +more complex than that. The only service that the log provides is discovery of +signed checksums. Key transparency projects have their own merit. + +The log will _try_ to incorporate a leaf into the Merkle tree if a logging +request is accepted. There are no _promises of public logging_ as in +Certificate Transparency. Therefore, the submitter needs to wait for an +inclusion proof before concluding that the request succeeded. Not having +inclusion promises makes the log less complex. + +#### Step 3 - distributing proofs of public logging +The data publisher is responsible for collecting all cryptographic proofs that +their end-users will need to enforce public logging. It must be possible to +download the following collection (in)directly from the data publisher: +1. **Shard hint**: the data publisher's selected shard hint. +2. **Opaque data**: the data publisher's opaque data. +3. **Signature**: the data publisher's leaf signature. +5. **Cosigned tree head**: the log's tree head and a _list of signatures_ that +state it is consistent with prior history. +6. **Inclusion proof**: a proof of inclusion that is based on the leaf and tree +head in question. + +The public verification key is known. Therefore, the first three fields are +sufficient to reconstruct the logged leaf. The leaf's signature can be +verified. The final two fields then prove that the leaf is in the log. If the +leaf is included in the log, any monitor can detect that there is a new +signature for a data publisher's public verification key. + +The catch is that the proof of logging is only as convincing as the tree head +that the inclusion proof leads up to. To bypass public logging, the attacker +needs to control a threshold of independent _witnesses_ that cosign the log. A +benign witness will only sign the log's tree head if it is consistent with prior +history. + +#### Summary +The log is sharded and will shutdown at a predefined time. The log can shut +down _safely_ because end-user verification is not interactive. The difficulty +of bypassing public logging is based on the difficulty of controlling a +threshold of independent witnesses. Witnesses cosign tree heads to make them +trustworthy. + +Submitters, monitors, and witnesses interact with the log using an HTTP(S) API. +Submitters must prove that they own a domain name as an anti-spam mechanism. +End-users interact with the log _indirectly_ via a data publisher. It is the +data publisher's job to log signed checksums, distribute necessary proofs of +logging, and monitor the log. + +### A peak into the details +Our bird's view introduction skipped many details that matter in practise. Some +of these details are presented here using a question-answer format. A +question-answer format is helpful because it is easily modified and extended. + +#### What cryptographic primitives are supported? +The only supported hash algorithm is SHA256. The only supported signature +scheme is Ed25519. Not having any cryptographic agility makes the protocol +simpler and more secure. + +An immediate follow-up question is how that is supposed to work with existing +and future signature tooling. The key insight is that _additional tooling is +already required to verify Merkle tree proofs. That tooling should use SHA256. +That tooling should also verify all Ed25519 signatures that logs, witnesses, and +data publishers create_. + +For example, suppose that an ecosystem uses `gpg` which has its own incompatible +signature format and algorithms. The data publisher could _cross-sign_ using +Ed25519 as follows: +1. Sign the opaque data as you normally would with `gpg`. +2. Hash the opaque data and use that as the leaf's checksum. Sign the leaf +using Ed25519. + +First the end-user verifies that the `gpg` signature is valid. This is the +old verification process. Then the end-user uses the additional tooling to +verify proofs of logging, which involves SHA256 hashing and Ed25519 signatures. + +The downside is that the data publisher may need to manage an Ed25519 key _as +well_. TODO: motivate why that is a suboptimal but worth-while trade-off. + +#### What (de)serialization parsers are needed? +#### Why witness cosigning? +#### What policy should be used? +#### TODO +Add more key questions and answers. + +## Concluding remarks +Example of binary transparency and reproducible builds. -- cgit v1.2.3 From 6cae1445318e22ce909b0211fc405dbeb6db7c44 Mon Sep 17 00:00:00 2001 From: Rasmus Dahlberg Date: Fri, 30 Apr 2021 12:11:40 +0200 Subject: fixed typos --- doc/design.md | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/doc/design.md b/doc/design.md index 9fcf4b6..cb379e5 100644 --- a/doc/design.md +++ b/doc/design.md @@ -1,6 +1,6 @@ # System Transparency Logging: Design v0 We propose System Transparency logging. It is similar to Certificate -Transparency, expect that cryptographically signed checksums are logged as +Transparency, except that cryptographically signed checksums are logged as opposed to X.509 certificates. Publicly logging signed checksums allow anyone to discover which keys produced what signatures. As such, malicious and unintended key-usage can be _detected_. We present our design and conclude by @@ -31,7 +31,7 @@ is to facilitate detection of unwanted key-usage. We consider a powerful attacker that gained control of a target's signing and release infrastructure. This covers a weaker form of attacker that is able to sign data and distribute it to a subset of isolated users. For example, this is -essentially what FBI requested from Apple in the San Bernardino case [\[FBI-Apple\]](https://www.eff.org/cases/apple-challenges-fbi-all-writs-act-order). +essentially what the FBI requested from Apple in the San Bernardino case [\[FBI-Apple\]](https://www.eff.org/cases/apple-challenges-fbi-all-writs-act-order). The fact that signing keys and related infrastructure components get compromised should not be controversial these days [\[SolarWinds\]](https://www.zdnet.com/article/third-malware-strain-discovered-in-solarwinds-supply-chain-attack/). @@ -57,7 +57,7 @@ independent parties are adversarial. It is a non-goal to disclose the data that a checksum represents. For example, the log cannot distinguish between a checksum that represents a tax declaration, an ISO image, or a Debian package. This means that the type of detection we -support is more _course-grained_ when compared to Certificate Transparency. +support is more _coarse-grained_ when compared to Certificate Transparency. ## Design We consider a data publisher that wants to digitally sign their data. The data @@ -69,7 +69,7 @@ signature tooling. The ecosystem at large can continue to use `gpg`, `openssl`, We _have to assume_ that additional tooling can be installed by end-users that wish to enforce transparency logging. For example, none of the existing -signature tooling support verification of Merkle tree proofs. A side-effect of +signature tooling supports verification of Merkle tree proofs. A side-effect of our design is that this additional tooling makes no outbound connections. The above data flows are thus preserved. @@ -78,7 +78,7 @@ A central part of any transparency log is the data. The data is stored by the leaves of an append-only Merkle tree. Our leaf structure contains four fields: - **shard_hint**: a number that binds the leaf to a particular _shard interval_. Sharding means that the log has a predefined time during which logging requests -will be accepted. Once elapsed, the log can be shutdown. +will be accepted. Once elapsed, the log can be shut down. - **checksum**: a cryptographic hash of some opaque data. The log never sees the opaque data; just the hash. - **signature**: a digital signature that is computed by the data publisher over @@ -166,7 +166,7 @@ benign witness will only sign the log's tree head if it is consistent with prior history. #### Summary -The log is sharded and will shutdown at a predefined time. The log can shut +The log is sharded and will shut down at a predefined time. The log can shut down _safely_ because end-user verification is not interactive. The difficulty of bypassing public logging is based on the difficulty of controlling a threshold of independent witnesses. Witnesses cosign tree heads to make them @@ -178,7 +178,7 @@ End-users interact with the log _indirectly_ via a data publisher. It is the data publisher's job to log signed checksums, distribute necessary proofs of logging, and monitor the log. -### A peak into the details +### A peek into the details Our bird's view introduction skipped many details that matter in practise. Some of these details are presented here using a question-answer format. A question-answer format is helpful because it is easily modified and extended. -- cgit v1.2.3 From 984f73e11ea1000b3af4f36199f591450afca2af Mon Sep 17 00:00:00 2001 From: Rasmus Dahlberg Date: Fri, 30 Apr 2021 14:15:50 +0200 Subject: clarified why domain hint is not in the leaf --- doc/design.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/doc/design.md b/doc/design.md index cb379e5..dda9efe 100644 --- a/doc/design.md +++ b/doc/design.md @@ -132,8 +132,8 @@ ecosystem needs more than one log to be reliable. DNS also has built-in caching that can be influenced by setting TTLs accordingly. The submitter's domain hint is not part of the leaf because key management is -more complex than that. The only service that the log provides is discovery of -signed checksums. Key transparency projects have their own merit. +more complex than that. A separate project should focus on transparent key +management. The scope of our work is transparent _key-usage_. The log will _try_ to incorporate a leaf into the Merkle tree if a logging request is accepted. There are no _promises of public logging_ as in -- cgit v1.2.3 From b78c5a72cd6284b5be3cf4e42fd85b7f16cb0dc4 Mon Sep 17 00:00:00 2001 From: Rasmus Dahlberg Date: Fri, 30 Apr 2021 14:32:10 +0200 Subject: rephrased a complex sentence --- doc/design.md | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/doc/design.md b/doc/design.md index dda9efe..c7be178 100644 --- a/doc/design.md +++ b/doc/design.md @@ -143,14 +143,14 @@ inclusion promises makes the log less complex. #### Step 3 - distributing proofs of public logging The data publisher is responsible for collecting all cryptographic proofs that -their end-users will need to enforce public logging. It must be possible to -download the following collection (in)directly from the data publisher: -1. **Shard hint**: the data publisher's selected shard hint. -2. **Opaque data**: the data publisher's opaque data. +their end-users will need to enforce public logging. The collection below +should be downloadable from the same place that the data is normally hosted. +1. **Opaque data**: the data publisher's opaque data. +2. **Shard hint**: the data publisher's selected shard hint. 3. **Signature**: the data publisher's leaf signature. -5. **Cosigned tree head**: the log's tree head and a _list of signatures_ that +4. **Cosigned tree head**: the log's tree head and a _list of signatures_ that state it is consistent with prior history. -6. **Inclusion proof**: a proof of inclusion that is based on the leaf and tree +5. **Inclusion proof**: a proof of inclusion that is based on the leaf and tree head in question. The public verification key is known. Therefore, the first three fields are -- cgit v1.2.3 From 6de2935d3a6589d35a6e7a59c56c5a67313f3ccb Mon Sep 17 00:00:00 2001 From: Rasmus Dahlberg Date: Fri, 30 Apr 2021 14:34:38 +0200 Subject: minor edit --- doc/design.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/doc/design.md b/doc/design.md index c7be178..0aa83f2 100644 --- a/doc/design.md +++ b/doc/design.md @@ -186,7 +186,7 @@ question-answer format is helpful because it is easily modified and extended. #### What cryptographic primitives are supported? The only supported hash algorithm is SHA256. The only supported signature scheme is Ed25519. Not having any cryptographic agility makes the protocol -simpler and more secure. +less complex and more secure. An immediate follow-up question is how that is supposed to work with existing and future signature tooling. The key insight is that _additional tooling is -- cgit v1.2.3 From f649f2715dc6c4c7f45116b83a6347a08d7193b4 Mon Sep 17 00:00:00 2001 From: Rasmus Dahlberg Date: Sat, 1 May 2021 15:15:22 +0200 Subject: removed unnecessary parser details in the bird's view --- doc/design.md | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/doc/design.md b/doc/design.md index 0aa83f2..2836364 100644 --- a/doc/design.md +++ b/doc/design.md @@ -108,10 +108,8 @@ mistake_. #### Step 2 - submitting a logging request The log implements an HTTP(S) API. Input and output is human-readable and uses -percent encoding. We decided to use percent encoding for requests and responses -because it is a simple format that is commonly used on the web. A more complex -parser like JSON is not needed if the exchanged data structures are basic -enough. +a simple key-value format. A more complex parser like JSON is not needed +because the exchanged data structures are basic enough. The data publisher submits their shard hint, checksum, signature, and public verification key as key-value pairs. The log will use the public verification -- cgit v1.2.3 From e61bd2fb0e845eeef11b1825fdbc5e5c52fb2ec5 Mon Sep 17 00:00:00 2001 From: Rasmus Dahlberg Date: Sat, 1 May 2021 19:39:45 +0200 Subject: added context regarding the supported cryptographic primitives --- doc/design.md | 49 ++++++++++++++++++++++++++++--------------------- 1 file changed, 28 insertions(+), 21 deletions(-) diff --git a/doc/design.md b/doc/design.md index 2836364..91de288 100644 --- a/doc/design.md +++ b/doc/design.md @@ -183,32 +183,39 @@ question-answer format is helpful because it is easily modified and extended. #### What cryptographic primitives are supported? The only supported hash algorithm is SHA256. The only supported signature -scheme is Ed25519. Not having any cryptographic agility makes the protocol -less complex and more secure. - -An immediate follow-up question is how that is supposed to work with existing -and future signature tooling. The key insight is that _additional tooling is -already required to verify Merkle tree proofs. That tooling should use SHA256. -That tooling should also verify all Ed25519 signatures that logs, witnesses, and -data publishers create_. - -For example, suppose that an ecosystem uses `gpg` which has its own incompatible -signature format and algorithms. The data publisher could _cross-sign_ using -Ed25519 as follows: -1. Sign the opaque data as you normally would with `gpg`. +scheme is Ed25519. Not having any cryptographic agility makes the protocol less +complex and more secure. + +We can be cryptographically opinionated because of a key insight. Existing +signature tools like `gpg`, `ssh-keygen -Y`, and `signify` cannot verify proofs +of public logging. Therefore, _additional tooling must already be installed by +end-users_. That tooling should verify hashes using the log's hash function. +That tooling should also verify signatures using the log's signature scheme. +Signed messages include tree heads as well as tree leaves. + +#### Why not let the data publisher pick their own signature scheme and format? +Agility introduces complexity and difficult policy questions. For example, +which algorithms and formats should (not) be supported and why? Picking Ed25519 +is a current best practise that should be encouraged if possible. + +There is not much we can do if a data publisher _refuses_ to rely on the log's +hash function or signature scheme. + +#### What if the data publisher must use a specific signature scheme or format? +You may _cross-sign_ the data as follows. +1. Sign the opaque data as you normally would. 2. Hash the opaque data and use that as the leaf's checksum. Sign the leaf -using Ed25519. +using the log's signature scheme. -First the end-user verifies that the `gpg` signature is valid. This is the -old verification process. Then the end-user uses the additional tooling to -verify proofs of logging, which involves SHA256 hashing and Ed25519 signatures. - -The downside is that the data publisher may need to manage an Ed25519 key _as -well_. TODO: motivate why that is a suboptimal but worth-while trade-off. +First the end-user verifies that the normal signature is valid. Then the +end-user lets the additional tooling (that is already required) verify the rest. +Cross-signing should be a relatively comfortable upgrade path that is backwards +compatible. The downside is that the data publisher may need to manage an +additional key-pair. #### What (de)serialization parsers are needed? -#### Why witness cosigning? #### What policy should be used? +#### Why witness cosigning? #### TODO Add more key questions and answers. -- cgit v1.2.3 From 16eed32e779f2fef850c084cb2631898dddcc5dc Mon Sep 17 00:00:00 2001 From: Rasmus Dahlberg Date: Sat, 1 May 2021 19:46:52 +0200 Subject: added q/a topics --- doc/design.md | 3 +++ 1 file changed, 3 insertions(+) diff --git a/doc/design.md b/doc/design.md index 91de288..22bfab0 100644 --- a/doc/design.md +++ b/doc/design.md @@ -218,6 +218,9 @@ additional key-pair. #### Why witness cosigning? #### TODO Add more key questions and answers. +- Log spamming +- Log poisoning +- Why we removed identifier field from the leaf ## Concluding remarks Example of binary transparency and reproducible builds. -- cgit v1.2.3 From 8f76216554d83cf45094686f6a43f757d2c186fe Mon Sep 17 00:00:00 2001 From: Rasmus Dahlberg Date: Mon, 3 May 2021 10:47:57 +0200 Subject: added detail that needs to be explained --- doc/design.md | 1 + 1 file changed, 1 insertion(+) diff --git a/doc/design.md b/doc/design.md index 22bfab0..bd24878 100644 --- a/doc/design.md +++ b/doc/design.md @@ -221,6 +221,7 @@ Add more key questions and answers. - Log spamming - Log poisoning - Why we removed identifier field from the leaf +- Explain `latest`, `stable` and `cosigned` tree head. ## Concluding remarks Example of binary transparency and reproducible builds. -- cgit v1.2.3 From 57a600662e98f86fc103f2671a5ec9602e1b7dd0 Mon Sep 17 00:00:00 2001 From: Linus Nordberg Date: Mon, 3 May 2021 15:38:51 +0200 Subject: Incorporate changes from recent discussions. Remove all RSA support. Motivation: Simpler format for tree_leaf. Replace percent-encoding with headers for indata and key/value in body for outdata. Motivation: ':' is exactly what we want and it works for output data (responses) and not only for input data (requests). Don't POST. Motivation: We don't need the complexity of POST since we don't ever send a lot of data to the log. Split up the get-signed-tree-head endpoint into three separate without input data. Motivation: More explicit API plus easier debugging. Change timestamps and shard hints to use seconds rather than milliseconds. Motivation: time(1) and time(2). --- doc/api.md | 190 ++++++++++++++++++++++++++++++++++--------------------------- 1 file changed, 105 insertions(+), 85 deletions(-) diff --git a/doc/api.md b/doc/api.md index 174d2c9..2d54001 100644 --- a/doc/api.md +++ b/doc/api.md @@ -1,25 +1,29 @@ # System Transparency Logging: API v0 This document describes details of the System Transparency logging API, version 0. The broader picture is not explained here. We assume that you have -read the System Transparency design document. It can be found [here](https://github.com/system-transparency/stfe/blob/design/doc/design.md). +read the System Transparency Logging design document. It can be found [here](https://github.com/system-transparency/stfe/blob/design/doc/design.md). **Warning.** This is a work-in-progress document that may be moved or modified. ## Overview The log implements an HTTP(S) API: -- Requests that add data to the log use the HTTP POST method. -- Request that retrieve data from the log use the HTTP GET method. -- The HTTP content type is `application/x-www-form-urlencoded` for requests and -responses. This means that all input and output are expressed as key-value -pairs. Binary data must be hex-encoded. - -We decided to use percent encoding for requests and responses because it is a -_simple format_ that is commonly used on the web. We are not using percent -encoding for signed and/or logged data. In other words, a submitter may -distribute log responses to their end-users in a different format that suit -them. The forced (de)serialization parser on _end-users_ is a small subset of -Trunnel. Trunnel is an "idiot-proof" wire-format that the Tor project uses. + +- Requests to the log use the HTTP GET method. +- Input data (in requests) and output data (in responses) are + expressed as ASCII-encoded key/value pairs. +- Requests use HTTP request headers for input data while responses use + the HTTP message body for output data. +- Binary data is hex-encoded before being transmitted. + +The motivation for using a text based key/value format for request and +response data is that it's simple to parse. Note that this format is not being +used for the serialization of signed or logged data, where a more +well defined and storage efficient format is desirable. +A submitter may distribute log responses to their end-users in any +format that suits them. The (de)serialization required for +_end-users_ is a small subset of Trunnel. Trunnel is an "idiot-proof" +wire-format in use by the Tor project. ## Primitives ### Cryptography @@ -32,19 +36,14 @@ All other parts that are not Merkle tree related also use SHA256 as the hash function. Using more than one hash function would increases the overall attack surface: two hash functions must be collision resistant instead of one. -We recommend that submitters sign using Ed25519. We also support RSA with -[deterministic](https://tools.ietf.org/html/rfc8017#section-8.2) -or [probabilistic](https://tools.ietf.org/html/rfc8017#section-8.1) -padding. Supporting RSA is suboptimal, but excluding it would make the log -useless for many possible adopters. - ### Serialization -Log requests and responses are percent encoded. Percent encoding is a smaller -dependency than an alternative parser like JSON. It is comparable to rolling -your own minimalistic line-terminated format. Some input and output data is -binary: cryptographic hashes and signatures. Binary data must be expressed as -hex before percent-encoding it. We decided to use hex as opposed to base64 -because it is simpler, favoring simplicity over efficiency on the wire. +Log requests and responses are transmitted as ASCII-encoded key/value +pairs, for a smaller dependency than an alternative parser like JSON. +Some input and output data is binary: cryptographic hashes and +signatures. Binary data must be Base16-encoded, also known as hex +encoding. Using hex as opposed to base64 is motivated by it being +simpler, favoring ease of decoding and encoding over efficiency on the +wire. We use the [Trunnel](https://gitweb.torproject.org/trunnel.git) [description language](https://www.seul.org/~nickm/trunnel-manual.html) to define (de)serialization of data structures that need to be signed or @@ -57,9 +56,9 @@ can be generated in C and Go. A fair summary of our Trunnel usage is as follows. -All integers are 64-bit, unsigned, and in network byte order. A fixed-size byte -array is put into the serialization buffer in-order, starting from the first -byte. A variable length byte array first declares its length as an integer, +All integers are 64-bit, unsigned, and in network byte order. Fixed-size byte +arrays are put into the serialization buffer in-order, starting from the first +byte. Variable length byte arrays first declare their length as an integer, which is then followed by that number of bytes. These basic types are concatenated to form a collection. You should not need a general-purpose Trunnel (de)serialization parser to work with this format. If you have one, you @@ -70,7 +69,7 @@ format explicit and unambiguous. Tree heads are signed by the log and its witnesses. It contains a timestamp, a tree size, and a root hash. The timestamp is included so that monitors can ensure _liveliness_. It is the time since the UNIX epoch (January 1, 1970 -00:00:00 UTC) in milliseconds. The tree size specifies the current number of +00:00:00 UTC) in seconds. The tree size specifies the current number of leaves. The root hash fixes the structure and content of the Merkle tree. ``` @@ -86,50 +85,25 @@ cosign a tree head if it is inconsistent with prior history or if the timestamp is backdated or future-dated more than 12 hours. #### Merkle tree leaf -The log supports a single leaf type. It contains a message, a signature scheme, -a signature that the submitter computed over the message, and a hash of the +The log supports a single leaf type. It contains a shard hint, a checksum over whatever the submitter wants to log a checksum for, +a signature that the submitter computed over the shard hint and the checksum, and a hash of the public verification key that can be used to verify the signature. ``` -const SIGNATURE_SCHEME_ED25519 = 1; // RFC 8032 -const SIGNATURE_SCHEME_RSASSA_PKCS1_V1_5 = 2; // RFC 8017 -const SIGNATURE_SCHEME_RSASSA_PSS = 3; // RFC 8017 - -struct signature_ed25519 { - u8 signature[32]; -}; - -struct signature_rsassa { - u64 num_bytes IN [ 256, 384, 512 ]; - u8 signature[num_bytes]; -}; - -struct message { +struct tree_leaf { u64 shard_hint; u8 checksum[32]; -}; - -struct tree_leaf { - struct message message; - u64 signature_scheme IN [ - SIGNATURE_SCHEME_ED25519, - SIGNATURE_SCHEME_RSASSA_PKCS1_V1_5, - SIGNATURE_SCHEME_RSASSA_PSS, - ]; - union signature[signature_scheme] { - SIGNATURE_SCHEME_ED25519: struct signature_ed25519 ed25519; - default: struct signature_rsassa rsassa; - } + u8 signature[32]; u8 key_hash[32]; } ``` -Unlike X.509 certificates that already have validity ranges, a checksum does not -have any such information. Therefore, we require that the submitter selects a +Unlike X.509 certificates which already have validity ranges, a checksum does not +carry any such information. Therefore, we require that the submitter selects a _shard hint_. The selected shard hint must be in the log's _shard interval_. A shard interval is defined by a start time and an end time. Both ends of the -shard interval are inclusive and expressed as the number of milliseconds since -the UNIX epoch (January 1, 1970 00:00:00 UTC). +shard interval are inclusive and expressed as the number of seconds since +the UNIX epoch (January 1, 1970 00:00 UTC). Sharding simplifies log operations because it becomes explicit when a log can be shutdown. A log must only accept logging requests that have valid shard hints. @@ -143,11 +117,15 @@ shard into a newer one. Not only would that defeat the purpose of sharding, but it would also become a potential denial-of-service vector. The signed message is composed of the selected shard hint and the submitter's -checksum. It must be possible to verify the signature using the specified -signature scheme and the submitter's public verification key. +checksum. It must be possible to verify the signature using the +submitter's public verification key. -A key-hash is included in the leaf so that it can be attributed to the signing -entity. A hash, rather than the full public verification key, is used to force +Note that the way `shard_hint` and `chekcsum` are serialized with +regards to signing differs from how they're being transmitted to the +log. + +A key hash is included in the leaf so that the leaf can be attributed to the +submitter. A hash, rather than the full public verification key, is used to motivate the verifier to locate the appropriate key and make an explicit trust decision. ## Public endpoints @@ -162,32 +140,76 @@ human-readable value that describes what went wrong. For example, `error=invalid+signature`, `error=rate+limit+exceeded`, or `error=unknown+leaf+hash`. -### get-signed-tree-head +### get-tree-head-cosigned +Returns the latest cosigned tree head. Used by ordinary users of the log. + ``` -GET /st/v0/get-signed-tree-head +GET /st/v0/get-tree-head-cosigned ``` Input: -- "type": either the string "latest", "stable", or "cosigned". - - latest: ask for the most recent signed tree head. - - stable: ask for a recent signed tree head that is fixed for some period - of time. - - cosigned: ask for a recent cosigned tree head. +- None Output on success: -- "timestamp": `tree_head.timestamp` as a human-readable number. -- "tree_size": `tree_head.tree_size` as a human-readable number. -- "root_hash": `tree_head.root_hash` in hex. -- "signature": an Ed25519 signature over `tree_head`. The result is -hex-encoded. +- "timestamp": `tree_head.timestamp` ASCII-encoded decimal number, seconds since the UNIX epoch. +- "tree_size": `tree_head.tree_size` ASCII-encoded decimal number. +- "root_hash": `tree_head.root_hash` hex-encoded. +- "signature": hex-encoded Ed25519 signature over `tree_head` serialzed as described in section `Merkle tree head`. - "key_hash": a hash of the public verification key that can be used to verify -the signature. The public verification key is serialized as in RFC 8032, then -hashed using SHA256. The result is hex-encoded. +the signature. The key is encoded as defined in [RFC 8032, section 5.1.2](https://tools.ietf.org/html/rfc8032#section-5.1.2), and then +hashed using SHA256. The hash value is hex-encoded. The "signature" and "key_hash" fields may repeat. The first signature corresponds to the first key hash, the second signature corresponds to the second key hash, etc. The number of signatures and key hashes must match. +### get-tree-head-to-sign +Returns the latest tree head to be signed by log witnesses. Used by +witnesses. + +``` +GET /st/v0/get-tree-head-to-sign +``` + +Input: +- None + +Output on success: +- "timestamp": `tree_head.timestamp` ASCII-encoded decimal number, seconds since the UNIX epoch. +- "tree_size": `tree_head.tree_size` ASCII-encoded decimal number. +- "root_hash": `tree_head.root_hash` hex-encoded. +- "signature": hex-encoded Ed25519 signature over `tree_head` serialzed as described in section `Merkle tree head`. +- "key_hash": a hash of the public verification key that can be used to verify +the signature. The key is encoded as defined in [RFC 8032, section 5.1.2](https://tools.ietf.org/html/rfc8032#section-5.1.2), and then +hashed using SHA256. The hash value is hex-encoded. + +There is exactly one `signature` and one `key_hash` field. The +`key_hash` refers to the log's signing key. + + +### get-tree-head-latest +Returns the latest tree head, signed only by the log. Used for debug. + +``` +GET /st/v0/get-tree-head-latest +``` + +Input: +- None + +Output on success: +- "timestamp": `tree_head.timestamp` ASCII-encoded decimal number, seconds since the UNIX epoch. +- "tree_size": `tree_head.tree_size` ASCII-encoded decimal number. +- "root_hash": `tree_head.root_hash` hex-encoded. +- "signature": hex-encoded Ed25519 signature over `tree_head` serialzed as described in section `Merkle tree head`. +- "key_hash": a hash of the public verification key that can be used to verify +the signature. The key is encoded as defined in [RFC 8032, section 5.1.2](https://tools.ietf.org/html/rfc8032#section-5.1.2), and then +hashed using SHA256. The hash value is hex-encoded. + +There is exactly one `signature` and one `key_hash` field. The +`key_hash` refers to the log's signing key. + + ### get-proof-by-hash ``` POST /st/v0/get-proof-by-hash @@ -260,11 +282,9 @@ POST /st/v0/add-leaf ``` Input: -- "shard_hint": human-readable number in the log's shard interval that the +- "shard_hint": human-readable decimal number in the log's shard interval that the submitter selected. -- "checksum": the cryptographic checksum that the submitter wants to log in hex. -- "signature_scheme": human-readable number that identifies the submitter's -signature scheme. +- "checksum": the cryptographic checksum that the submitter wants to log in hex. note: fixed length 64 bytes, validated by the server somehow - "signature": the submitter's signature over `tree_leaf.message`. The result is hex-encoded. - "verification_key": the submitter's public verification key. It is serialized @@ -314,6 +334,6 @@ log's tree head signatures. - **Log identifier**: the hashed public verification key using SHA256. - **Shard interval**: the time during which the log accepts logging requests. The shard interval's start and end are inclusive and expressed as the number of -milliseconds since the UNIX epoch. +seconds since the UNIX epoch. - **Base URL**: where the log can be reached over HTTP(S). It is the prefix before a version-0 specific endpoint. -- cgit v1.2.3 From e7bd2f29e7226e39bee7d0a1b89965ef5bdf5dc2 Mon Sep 17 00:00:00 2001 From: Rasmus Dahlberg Date: Mon, 3 May 2021 22:48:17 +0200 Subject: added q/a topic --- doc/design.md | 1 + 1 file changed, 1 insertion(+) diff --git a/doc/design.md b/doc/design.md index bd24878..4c764e3 100644 --- a/doc/design.md +++ b/doc/design.md @@ -222,6 +222,7 @@ Add more key questions and answers. - Log poisoning - Why we removed identifier field from the leaf - Explain `latest`, `stable` and `cosigned` tree head. +- Privacy aspects ## Concluding remarks Example of binary transparency and reproducible builds. -- cgit v1.2.3 From c82c4e1266c5e8fbe08cb0f6140caea2723ef205 Mon Sep 17 00:00:00 2001 From: Linus Nordberg Date: Tue, 4 May 2021 14:51:36 +0200 Subject: be explicit with key type; define struct message, for tree_leaf Specify who's verification key -- log, witness or submitter. Move shard_hint and checksum in tree_leaf into its own struct, for a more explicit definition of what to be signed. --- doc/api.md | 59 ++++++++++++++++++++++++++++++++++------------------------- 1 file changed, 34 insertions(+), 25 deletions(-) diff --git a/doc/api.md b/doc/api.md index 2d54001..0fa445a 100644 --- a/doc/api.md +++ b/doc/api.md @@ -87,14 +87,18 @@ is backdated or future-dated more than 12 hours. #### Merkle tree leaf The log supports a single leaf type. It contains a shard hint, a checksum over whatever the submitter wants to log a checksum for, a signature that the submitter computed over the shard hint and the checksum, and a hash of the -public verification key that can be used to verify the signature. +submitter's public verification key, that can be used to verify the signature. ``` +struct message { + u64 shard_hint; + u8 checksum[32]; +}; + struct tree_leaf { - u64 shard_hint; - u8 checksum[32]; - u8 signature[32]; - u8 key_hash[32]; + struct message; + u8 signature_over_message[32]; + u8 key_hash[32]; } ``` @@ -116,17 +120,20 @@ Without a shard hint, the good Samaritan could log all leaves from an earlier shard into a newer one. Not only would that defeat the purpose of sharding, but it would also become a potential denial-of-service vector. -The signed message is composed of the selected shard hint and the submitter's -checksum. It must be possible to verify the signature using the -submitter's public verification key. +The signed message is composed of the chosen `shard_hint` and the +submitter's `checksum`. It must be possible to verify +`signature_over_message` using the submitter's public verification +key. -Note that the way `shard_hint` and `chekcsum` are serialized with +Note that the way `shard_hint` and `checksum` are serialized with regards to signing differs from how they're being transmitted to the log. -A key hash is included in the leaf so that the leaf can be attributed to the -submitter. A hash, rather than the full public verification key, is used to motivate -the verifier to locate the appropriate key and make an explicit trust decision. +A `key_hash` of the key used for signing `message` is included in +`tree_leaf` so that the leaf can be attributed to the submitter. A +hash, rather than the full public key, is used to motivate the +verifier to locate the appropriate key and make an explicit trust +decision. ## Public endpoints Every log has a base URL that identifies it uniquely. The only constraint is @@ -155,8 +162,8 @@ Output on success: - "tree_size": `tree_head.tree_size` ASCII-encoded decimal number. - "root_hash": `tree_head.root_hash` hex-encoded. - "signature": hex-encoded Ed25519 signature over `tree_head` serialzed as described in section `Merkle tree head`. -- "key_hash": a hash of the public verification key that can be used to verify -the signature. The key is encoded as defined in [RFC 8032, section 5.1.2](https://tools.ietf.org/html/rfc8032#section-5.1.2), and then +- "key_hash": a hash of the public verification key (belonging to either the log or to one of its witnesses), which can be used to verify +the most recent `signature`. The key is encoded as defined in [RFC 8032, section 5.1.2](https://tools.ietf.org/html/rfc8032#section-5.1.2), and then hashed using SHA256. The hash value is hex-encoded. The "signature" and "key_hash" fields may repeat. The first signature @@ -179,16 +186,16 @@ Output on success: - "tree_size": `tree_head.tree_size` ASCII-encoded decimal number. - "root_hash": `tree_head.root_hash` hex-encoded. - "signature": hex-encoded Ed25519 signature over `tree_head` serialzed as described in section `Merkle tree head`. -- "key_hash": a hash of the public verification key that can be used to verify -the signature. The key is encoded as defined in [RFC 8032, section 5.1.2](https://tools.ietf.org/html/rfc8032#section-5.1.2), and then +- "key_hash": a hash of the log's public verification key, which can be used to verify +`signature`. The key is encoded as defined in [RFC 8032, section 5.1.2](https://tools.ietf.org/html/rfc8032#section-5.1.2), and then hashed using SHA256. The hash value is hex-encoded. There is exactly one `signature` and one `key_hash` field. The -`key_hash` refers to the log's signing key. +`key_hash` refers to the log's public verification key. ### get-tree-head-latest -Returns the latest tree head, signed only by the log. Used for debug. +Returns the latest tree head, signed only by the log. Used for debugging purposes. ``` GET /st/v0/get-tree-head-latest @@ -202,12 +209,13 @@ Output on success: - "tree_size": `tree_head.tree_size` ASCII-encoded decimal number. - "root_hash": `tree_head.root_hash` hex-encoded. - "signature": hex-encoded Ed25519 signature over `tree_head` serialzed as described in section `Merkle tree head`. -- "key_hash": a hash of the public verification key that can be used to verify -the signature. The key is encoded as defined in [RFC 8032, section 5.1.2](https://tools.ietf.org/html/rfc8032#section-5.1.2), and then -hashed using SHA256. The hash value is hex-encoded. +- "key_hash": a hash of the log's public verification key that can be +used to verify `signature`. The key is encoded as defined in +[RFC 8032, section 5.1.2](https://tools.ietf.org/html/rfc8032#section-5.1.2), +and then hashed using SHA256. The hash value is hex-encoded. There is exactly one `signature` and one `key_hash` field. The -`key_hash` refers to the log's signing key. +`key_hash` refers to the log's public verification key. ### get-proof-by-hash @@ -317,9 +325,10 @@ POST /st/v0/add-cosignature Input: - "signature": an Ed25519 signature over `tree_head`. The result is hex-encoded. -- "key_hash": a hash of the public verification key that can be used to verify -the signature. The public verification key is serialized as in RFC 8032, then -hashed using SHA256. The result is hex-encoded. +- "key_hash": a hash of the witness' public verification key that can be used +to verify the signature. The key is encoded as defined in [RFC 8032, +section 5.1.2](https://tools.ietf.org/html/rfc8032#section-5.1.2), and +then hashed using SHA256. The hash value is hex-encoded. Output on success: - None -- cgit v1.2.3 From a30eb85272010ffbcfd3fb2c6932dc2f15d596c1 Mon Sep 17 00:00:00 2001 From: Linus Nordberg Date: Tue, 4 May 2021 14:53:00 +0200 Subject: get rid of the underspecified term "ordinary users" --- doc/api.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/doc/api.md b/doc/api.md index 0fa445a..976c167 100644 --- a/doc/api.md +++ b/doc/api.md @@ -148,7 +148,8 @@ human-readable value that describes what went wrong. For example, `error=unknown+leaf+hash`. ### get-tree-head-cosigned -Returns the latest cosigned tree head. Used by ordinary users of the log. +Returns the latest cosigned tree head. Used together with +`get-proof-by-hash` and `get-consistency-proof` for verifying the log. ``` GET /st/v0/get-tree-head-cosigned -- cgit v1.2.3 From c163da97d32a291a1c913800c926d7758c641c6e Mon Sep 17 00:00:00 2001 From: Linus Nordberg Date: Tue, 4 May 2021 14:53:36 +0200 Subject: specify serialization of key --- doc/api.md | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/doc/api.md b/doc/api.md index 976c167..362dc46 100644 --- a/doc/api.md +++ b/doc/api.md @@ -296,8 +296,7 @@ submitter selected. - "checksum": the cryptographic checksum that the submitter wants to log in hex. note: fixed length 64 bytes, validated by the server somehow - "signature": the submitter's signature over `tree_leaf.message`. The result is hex-encoded. -- "verification_key": the submitter's public verification key. It is serialized -as described in the corresponding RFC. The result is hex-encoded. +- "verification_key": the submitter's public verification key. The key is encoded as defined in [RFC 8032, section 5.1.2](https://tools.ietf.org/html/rfc8032#section-5.1.2). The result is hex-encoded. - "domain_hint": a domain name that indicates where `tree_leaf.key_hash` can be retrieved as a DNS TXT resource record in hex. -- cgit v1.2.3 From 9ee06539685bdcaea84b3daede5354d83264c1e4 Mon Sep 17 00:00:00 2001 From: Linus Nordberg Date: Tue, 4 May 2021 16:08:22 +0200 Subject: explain how input and output data are sent This is the "header in, body out" idea written up. We might change to a "POST body in, receive body out" scheme with "Content-Type: application/stfe" if we can decide that POST is not a terrible idea after all. --- doc/api.md | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/doc/api.md b/doc/api.md index 362dc46..c747aa2 100644 --- a/doc/api.md +++ b/doc/api.md @@ -141,6 +141,16 @@ that it must be a valid HTTP(S) URL that can have the `/st/v0/` suffix appended. For example, a complete endpoint URL could be `https://log.example.com/2021/st/v0/get-signed-tree-head`. +Input data (in requests) is sent as ASCII key/value pairs as HTTP +entity headers, with their keys prefixed with the string +`stlog-`. Example: For sending `treee_size=4711` as input a client +would send the HTTP header `stlog-tree_size: 4711`. + +Output data (in replies) is sent in the HTTP message body in the same +format as the input data, i.e. as ASCII key/value pairs on the format +`Key: Value`. Example: For sending `tree_size=4711` as output a log +would send an HTTP message body consisting of `stlog-tree_size: 4711`. + The HTTP status code is 200 OK to indicate success. A different HTTP status code is used to indicate failure. The log should set the "error" key to a human-readable value that describes what went wrong. For example, -- cgit v1.2.3 From 044dc540aaf3950695602e849906969aed7e6a46 Mon Sep 17 00:00:00 2001 From: Linus Nordberg Date: Tue, 4 May 2021 16:15:59 +0200 Subject: be consistent with "request" vs "entity" headers --- doc/api.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/doc/api.md b/doc/api.md index c747aa2..638b753 100644 --- a/doc/api.md +++ b/doc/api.md @@ -12,7 +12,7 @@ The log implements an HTTP(S) API: - Requests to the log use the HTTP GET method. - Input data (in requests) and output data (in responses) are expressed as ASCII-encoded key/value pairs. -- Requests use HTTP request headers for input data while responses use +- Requests use HTTP entity headers for input data while responses use the HTTP message body for output data. - Binary data is hex-encoded before being transmitted. -- cgit v1.2.3 From 3b5b4429d94e142ee12af7eb5f89b49997b72237 Mon Sep 17 00:00:00 2001 From: Linus Nordberg Date: Tue, 4 May 2021 16:22:32 +0200 Subject: whitespace changes --- doc/api.md | 323 ++++++++++++++++++++++++++++++++++--------------------------- 1 file changed, 181 insertions(+), 142 deletions(-) diff --git a/doc/api.md b/doc/api.md index 638b753..5b7cb19 100644 --- a/doc/api.md +++ b/doc/api.md @@ -1,7 +1,9 @@ # System Transparency Logging: API v0 -This document describes details of the System Transparency logging API, -version 0. The broader picture is not explained here. We assume that you have -read the System Transparency Logging design document. It can be found [here](https://github.com/system-transparency/stfe/blob/design/doc/design.md). +This document describes details of the System Transparency logging +API, version 0. The broader picture is not explained here. We assume +that you have read the System Transparency Logging design document. +It can be found +[here](https://github.com/system-transparency/stfe/blob/design/doc/design.md). **Warning.** This is a work-in-progress document that may be moved or modified. @@ -17,24 +19,28 @@ The log implements an HTTP(S) API: - Binary data is hex-encoded before being transmitted. The motivation for using a text based key/value format for request and -response data is that it's simple to parse. Note that this format is not being -used for the serialization of signed or logged data, where a more -well defined and storage efficient format is desirable. -A submitter may distribute log responses to their end-users in any +response data is that it's simple to parse. Note that this format is +not being used for the serialization of signed or logged data, where a +more well defined and storage efficient format is desirable. A +submitter may distribute log responses to their end-users in any format that suits them. The (de)serialization required for _end-users_ is a small subset of Trunnel. Trunnel is an "idiot-proof" wire-format in use by the Tor project. ## Primitives ### Cryptography -The log uses the same Merkle tree hash strategy as [RFC 6962, §2](https://tools.ietf.org/html/rfc6962#section-2). -The hash functions must be [SHA256](https://csrc.nist.gov/csrc/media/publications/fips/180/4/final/documents/fips180-4-draft-aug2014.pdf). -The log must sign tree heads using [Ed25519](https://tools.ietf.org/html/rfc8032). -The log's witnesses must also sign tree heads using Ed25519. - -All other parts that are not Merkle tree related also use SHA256 as the hash -function. Using more than one hash function would increases the overall attack -surface: two hash functions must be collision resistant instead of one. +The log uses the same Merkle tree hash strategy as +[RFC 6962,§2](https://tools.ietf.org/html/rfc6962#section-2). +The hash functions must be +[SHA256](https://csrc.nist.gov/csrc/media/publications/fips/180/4/final/documents/fips180-4-draft-aug2014.pdf). +The log must sign tree heads using +[Ed25519](https://tools.ietf.org/html/rfc8032). The log's witnesses +must also sign tree heads using Ed25519. + +All other parts that are not Merkle tree related also use SHA256 as +the hash function. Using more than one hash function would increases +the overall attack surface: two hash functions must be collision +resistant instead of one. ### Serialization Log requests and responses are transmitted as ASCII-encoded key/value @@ -45,32 +51,36 @@ encoding. Using hex as opposed to base64 is motivated by it being simpler, favoring ease of decoding and encoding over efficiency on the wire. -We use the [Trunnel](https://gitweb.torproject.org/trunnel.git) [description language](https://www.seul.org/~nickm/trunnel-manual.html) +We use the +[Trunnel](https://gitweb.torproject.org/trunnel.git) [description language](https://www.seul.org/~nickm/trunnel-manual.html) to define (de)serialization of data structures that need to be signed or inserted into the Merkle tree. Trunnel is more expressive than the [SSH wire format](https://tools.ietf.org/html/rfc4251#section-5). -It is about as expressive as the [TLS presentation language](https://tools.ietf.org/html/rfc8446#section-3). -A notable difference is that Trunnel supports integer constraints. The Trunnel -language is also readable by humans _and_ machines. "Obviously correct code" -can be generated in C and Go. +It is about as expressive as the +[TLS presentation language](https://tools.ietf.org/html/rfc8446#section-3). +A notable difference is that Trunnel supports integer constraints. +The Trunnel language is also readable by humans _and_ machines. +"Obviously correct code" can be generated in C and Go. A fair summary of our Trunnel usage is as follows. -All integers are 64-bit, unsigned, and in network byte order. Fixed-size byte -arrays are put into the serialization buffer in-order, starting from the first -byte. Variable length byte arrays first declare their length as an integer, -which is then followed by that number of bytes. These basic types are -concatenated to form a collection. You should not need a general-purpose -Trunnel (de)serialization parser to work with this format. If you have one, you -may use it though. The main point of using Trunnel is that it makes a simple -format explicit and unambiguous. +All integers are 64-bit, unsigned, and in network byte order. +Fixed-size byte arrays are put into the serialization buffer in-order, +starting from the first byte. Variable length byte arrays first +declare their length as an integer, which is then followed by that +number of bytes. These basic types are concatenated to form a +collection. You should not need a general-purpose Trunnel +(de)serialization parser to work with this format. If you have one, +you may use it though. The main point of using Trunnel is that it +makes a simple format explicit and unambiguous. #### Merkle tree head -Tree heads are signed by the log and its witnesses. It contains a timestamp, a -tree size, and a root hash. The timestamp is included so that monitors can -ensure _liveliness_. It is the time since the UNIX epoch (January 1, 1970 -00:00:00 UTC) in seconds. The tree size specifies the current number of -leaves. The root hash fixes the structure and content of the Merkle tree. +Tree heads are signed by the log and its witnesses. It contains a +timestamp, a tree size, and a root hash. The timestamp is included so +that monitors can ensure _liveliness_. It is the time since the UNIX +epoch (January 1, 1970 00:00:00 UTC) in seconds. The tree size +specifies the current number of leaves. The root hash fixes the +structure and content of the Merkle tree. ``` struct tree_head { @@ -80,14 +90,16 @@ struct tree_head { }; ``` -The serialized tree head must be signed using Ed25519. A witness must not -cosign a tree head if it is inconsistent with prior history or if the timestamp -is backdated or future-dated more than 12 hours. +The serialized tree head must be signed using Ed25519. A witness must +not cosign a tree head if it is inconsistent with prior history or if +the timestamp is backdated or future-dated more than 12 hours. #### Merkle tree leaf -The log supports a single leaf type. It contains a shard hint, a checksum over whatever the submitter wants to log a checksum for, -a signature that the submitter computed over the shard hint and the checksum, and a hash of the -submitter's public verification key, that can be used to verify the signature. +The log supports a single leaf type. It contains a shard hint, a +checksum over whatever the submitter wants to log a checksum for, a +signature that the submitter computed over the shard hint and the +checksum, and a hash of the submitter's public verification key, that +can be used to verify the signature. ``` struct message { @@ -102,23 +114,26 @@ struct tree_leaf { } ``` -Unlike X.509 certificates which already have validity ranges, a checksum does not -carry any such information. Therefore, we require that the submitter selects a -_shard hint_. The selected shard hint must be in the log's _shard interval_. A -shard interval is defined by a start time and an end time. Both ends of the -shard interval are inclusive and expressed as the number of seconds since -the UNIX epoch (January 1, 1970 00:00 UTC). - -Sharding simplifies log operations because it becomes explicit when a log can be -shutdown. A log must only accept logging requests that have valid shard hints. -A log should only accept logging requests during the predefined shard interval. -Note that _the submitter's shard hint is not a verified timestamp_. The -submitter should set the shard hint as large as possible. If a roughly verified -timestamp is needed, a cosigned tree head can be used. - -Without a shard hint, the good Samaritan could log all leaves from an earlier -shard into a newer one. Not only would that defeat the purpose of sharding, but -it would also become a potential denial-of-service vector. +Unlike X.509 certificates which already have validity ranges, a +checksum does not carry any such information. Therefore, we require +that the submitter selects a _shard hint_. The selected shard hint +must be in the log's _shard interval_. A shard interval is defined by +a start time and an end time. Both ends of the shard interval are +inclusive and expressed as the number of seconds since the UNIX epoch +(January 1, 1970 00:00 UTC). + +Sharding simplifies log operations because it becomes explicit when a +log can be shutdown. A log must only accept logging requests that +have valid shard hints. A log should only accept logging requests +during the predefined shard interval. Note that _the submitter's +shard hint is not a verified timestamp_. The submitter should set the +shard hint as large as possible. If a roughly verified timestamp is +needed, a cosigned tree head can be used. + +Without a shard hint, the good Samaritan could log all leaves from an +earlier shard into a newer one. Not only would that defeat the +purpose of sharding, but it would also become a potential +denial-of-service vector. The signed message is composed of the chosen `shard_hint` and the submitter's `checksum`. It must be possible to verify @@ -136,9 +151,10 @@ verifier to locate the appropriate key and make an explicit trust decision. ## Public endpoints -Every log has a base URL that identifies it uniquely. The only constraint is -that it must be a valid HTTP(S) URL that can have the `/st/v0/` suffix -appended. For example, a complete endpoint URL could be +Every log has a base URL that identifies it uniquely. The only +constraint is that it must be a valid HTTP(S) URL that can have the +`/st/v0/` suffix appended. For example, a complete endpoint +URL could be `https://log.example.com/2021/st/v0/get-signed-tree-head`. Input data (in requests) is sent as ASCII key/value pairs as HTTP @@ -151,11 +167,11 @@ format as the input data, i.e. as ASCII key/value pairs on the format `Key: Value`. Example: For sending `tree_size=4711` as output a log would send an HTTP message body consisting of `stlog-tree_size: 4711`. -The HTTP status code is 200 OK to indicate success. A different HTTP status -code is used to indicate failure. The log should set the "error" key to a -human-readable value that describes what went wrong. For example, -`error=invalid+signature`, `error=rate+limit+exceeded`, or -`error=unknown+leaf+hash`. +The HTTP status code is 200 OK to indicate success. A different HTTP +status code is used to indicate failure. The log should set the +"error" key to a human-readable value that describes what went wrong. +For example, `error=invalid+signature`, `error=rate+limit+exceeded`, +or `error=unknown+leaf+hash`. ### get-tree-head-cosigned Returns the latest cosigned tree head. Used together with @@ -169,17 +185,22 @@ Input: - None Output on success: -- "timestamp": `tree_head.timestamp` ASCII-encoded decimal number, seconds since the UNIX epoch. +- "timestamp": `tree_head.timestamp` ASCII-encoded decimal number, + seconds since the UNIX epoch. - "tree_size": `tree_head.tree_size` ASCII-encoded decimal number. - "root_hash": `tree_head.root_hash` hex-encoded. -- "signature": hex-encoded Ed25519 signature over `tree_head` serialzed as described in section `Merkle tree head`. -- "key_hash": a hash of the public verification key (belonging to either the log or to one of its witnesses), which can be used to verify -the most recent `signature`. The key is encoded as defined in [RFC 8032, section 5.1.2](https://tools.ietf.org/html/rfc8032#section-5.1.2), and then -hashed using SHA256. The hash value is hex-encoded. +- "signature": hex-encoded Ed25519 signature over `tree_head` + serialzed as described in section `Merkle tree head`. +- "key_hash": a hash of the public verification key (belonging to + either the log or to one of its witnesses), which can be used to + verify the most recent `signature`. The key is encoded as defined + in [RFC 8032, section 5.1.2](https://tools.ietf.org/html/rfc8032#section-5.1.2), + and then hashed using SHA256. The hash value is hex-encoded. The "signature" and "key_hash" fields may repeat. The first signature -corresponds to the first key hash, the second signature corresponds to the -second key hash, etc. The number of signatures and key hashes must match. +corresponds to the first key hash, the second signature corresponds to +the second key hash, etc. The number of signatures and key hashes +must match. ### get-tree-head-to-sign Returns the latest tree head to be signed by log witnesses. Used by @@ -193,20 +214,24 @@ Input: - None Output on success: -- "timestamp": `tree_head.timestamp` ASCII-encoded decimal number, seconds since the UNIX epoch. +- "timestamp": `tree_head.timestamp` ASCII-encoded decimal number, + seconds since the UNIX epoch. - "tree_size": `tree_head.tree_size` ASCII-encoded decimal number. - "root_hash": `tree_head.root_hash` hex-encoded. -- "signature": hex-encoded Ed25519 signature over `tree_head` serialzed as described in section `Merkle tree head`. -- "key_hash": a hash of the log's public verification key, which can be used to verify -`signature`. The key is encoded as defined in [RFC 8032, section 5.1.2](https://tools.ietf.org/html/rfc8032#section-5.1.2), and then -hashed using SHA256. The hash value is hex-encoded. +- "signature": hex-encoded Ed25519 signature over `tree_head` + serialzed as described in section `Merkle tree head`. +- "key_hash": a hash of the log's public verification key, which can + be used to verify `signature`. The key is encoded as defined in + [RFC 8032, section 5.1.2](https://tools.ietf.org/html/rfc8032#section-5.1.2), + and then hashed using SHA256. The hash value is hex-encoded. There is exactly one `signature` and one `key_hash` field. The `key_hash` refers to the log's public verification key. ### get-tree-head-latest -Returns the latest tree head, signed only by the log. Used for debugging purposes. +Returns the latest tree head, signed only by the log. Used for +debugging purposes. ``` GET /st/v0/get-tree-head-latest @@ -216,14 +241,16 @@ Input: - None Output on success: -- "timestamp": `tree_head.timestamp` ASCII-encoded decimal number, seconds since the UNIX epoch. +- "timestamp": `tree_head.timestamp` ASCII-encoded decimal number, + seconds since the UNIX epoch. - "tree_size": `tree_head.tree_size` ASCII-encoded decimal number. - "root_hash": `tree_head.root_hash` hex-encoded. -- "signature": hex-encoded Ed25519 signature over `tree_head` serialzed as described in section `Merkle tree head`. +- "signature": hex-encoded Ed25519 signature over `tree_head` + serialzed as described in section `Merkle tree head`. - "key_hash": a hash of the log's public verification key that can be -used to verify `signature`. The key is encoded as defined in -[RFC 8032, section 5.1.2](https://tools.ietf.org/html/rfc8032#section-5.1.2), -and then hashed using SHA256. The hash value is hex-encoded. + used to verify `signature`. The key is encoded as defined in + [RFC 8032, section 5.1.2](https://tools.ietf.org/html/rfc8032#section-5.1.2), + and then hashed using SHA256. The hash value is hex-encoded. There is exactly one `signature` and one `key_hash` field. The `key_hash` refers to the log's public verification key. @@ -235,21 +262,22 @@ POST /st/v0/get-proof-by-hash ``` Input: -- "leaf_hash": a hex-encoded leaf hash that identifies which `tree_leaf` the -log should prove inclusion for. The leaf hash is computed using the RFC 6962 -hashing strategy. In other words, `SHA256(0x00 | tree_leaf)`. -- "tree_size": a human-readable tree size of the tree head that the proof should -be based on. +- "leaf_hash": a hex-encoded leaf hash that identifies which + `tree_leaf` the log should prove inclusion for. The leaf hash is + computed using the RFC 6962 hashing strategy. In other words, + `SHA256(0x00 | tree_leaf)`. +- "tree_size": a human-readable tree size of the tree head that the + proof should be based on. Output on success: - "tree_size": human-readable tree size that the proof is based on. -- "leaf_index": human-readable zero-based index of the leaf that the proof is -based on. +- "leaf_index": human-readable zero-based index of the leaf that the + proof is based on. - "inclusion_path": a node hash in hex. -The "inclusion_path" may be omitted or repeated to represent an inclusion proof -of zero or more node hashes. The order of node hashes follow from our hash -strategy, see RFC 6962. +The "inclusion_path" may be omitted or repeated to represent an +inclusion proof of zero or more node hashes. The order of node hashes +follow from our hash strategy, see RFC 6962. ### get-consistency-proof ``` @@ -258,19 +286,19 @@ POST /st/v0/get-consistency-proof Input: - "new_size": human-readable tree size of a newer tree head. -- "old_size": human-readable tree size of an older tree head that the log should -prove is consistent with the newer tree head. +- "old_size": human-readable tree size of an older tree head that the + log should prove is consistent with the newer tree head. Output on success: -- "new_size": human-readable tree size of a newer tree head that the proof -is based on. -- "old_size": human-readable tree size of an older tree head that the proof is -based on. +- "new_size": human-readable tree size of a newer tree head that the + proof is based on. +- "old_size": human-readable tree size of an older tree head that the + proof is based on. - "consistency_path": a node hash in hex. -The "consistency_path" may be omitted or repeated to represent a consistency -proof of zero or more node hashes. The order of node hashes follow from our -hash strategy, see RFC 6962. +The "consistency_path" may be omitted or repeated to represent a +consistency proof of zero or more node hashes. The order of node +hashes follow from our hash strategy, see RFC 6962. ### get-leaves ``` @@ -282,18 +310,21 @@ Input: - "end_size": human-readable index of the last leaf to retrieve. Output on success: -- "shard_hint": `tree_leaf.message.shard_hint` as a human-readable number. +- "shard_hint": `tree_leaf.message.shard_hint` as a human-readable + number. - "checksum": `tree_leaf.message.checksum` in hex. -- "signature_scheme": human-readable number that identifies a signature scheme. +- "signature_scheme": human-readable number that identifies a + signature scheme. - "signature": `tree_leaf.signature` in hex. - "key_hash": `tree_leaf.key_hash` in hex. -All fields may be repeated to return more than one leaf. The first value in -each list refers to the first leaf, the second value in each list refers to the -second leaf, etc. The size of each list must match. +All fields may be repeated to return more than one leaf. The first +value in each list refers to the first leaf, the second value in each +list refers to the second leaf, etc. The size of each list must +match. -The log may return fewer leaves than requested. At least one leaf must be -returned on HTTP status code 200 OK. +The log may return fewer leaves than requested. At least one leaf +must be returned on HTTP status code 200 OK. ### add-leaf ``` @@ -301,31 +332,38 @@ POST /st/v0/add-leaf ``` Input: -- "shard_hint": human-readable decimal number in the log's shard interval that the -submitter selected. -- "checksum": the cryptographic checksum that the submitter wants to log in hex. note: fixed length 64 bytes, validated by the server somehow -- "signature": the submitter's signature over `tree_leaf.message`. The result -is hex-encoded. -- "verification_key": the submitter's public verification key. The key is encoded as defined in [RFC 8032, section 5.1.2](https://tools.ietf.org/html/rfc8032#section-5.1.2). The result is hex-encoded. -- "domain_hint": a domain name that indicates where `tree_leaf.key_hash` can be -retrieved as a DNS TXT resource record in hex. +- "shard_hint": human-readable decimal number in the log's shard + interval that the submitter selected. +- "checksum": the cryptographic checksum that the submitter wants to + log in hex. note: fixed length 64 bytes, validated by the server + somehow +- "signature": the submitter's signature over `tree_leaf.message`. + The result is hex-encoded. +- "verification_key": the submitter's public verification key. The + key is encoded as defined in + [RFC 8032, section 5.1.2](https://tools.ietf.org/html/rfc8032#section-5.1.2). The result is hex-encoded. +- "domain_hint": a domain name that indicates where + `tree_leaf.key_hash` can be retrieved as a DNS TXT resource record + in hex. Output on success: - None -The submitted entry will not be accepted if the signature is invalid or if the -downloaded verification-key hash does not match. The submitted entry may also -not be accepted if the second-level domain name exceeded its rate limit. By -coupling every add-leaf request with a second-level domain, it becomes more -difficult to spam the log. You would need an excessive number of domain names. -This becomes costly if free domain names are rejected. +The submitted entry will not be accepted if the signature is invalid +or if the downloaded verification-key hash does not match. The +submitted entry may also not be accepted if the second-level domain +name exceeded its rate limit. By coupling every add-leaf request with +a second-level domain, it becomes more difficult to spam the log. You +would need an excessive number of domain names. This becomes costly +if free domain names are rejected. -The log does not publish domain-name to key bindings because key management is -more complex than that. +The log does not publish domain-name to key bindings because key +management is more complex than that. -Public logging should not be assumed until an inclusion proof is available. An -inclusion proof should not be relied upon unless it leads up to a trustworthy -signed tree head. Witness cosigning can make a tree head trustworthy. +Public logging should not be assumed until an inclusion proof is +available. An inclusion proof should not be relied upon unless it +leads up to a trustworthy signed tree head. Witness cosigning can +make a tree head trustworthy. ### add-cosignature ``` @@ -334,25 +372,26 @@ POST /st/v0/add-cosignature Input: - "signature": an Ed25519 signature over `tree_head`. The result is -hex-encoded. -- "key_hash": a hash of the witness' public verification key that can be used -to verify the signature. The key is encoded as defined in [RFC 8032, -section 5.1.2](https://tools.ietf.org/html/rfc8032#section-5.1.2), and -then hashed using SHA256. The hash value is hex-encoded. + hex-encoded. +- "key_hash": a hash of the witness' public verification key that can + be used to verify the signature. The key is encoded as defined in + [RFC 8032, section 5.1.2](https://tools.ietf.org/html/rfc8032#section-5.1.2), + and then hashed using SHA256. The hash value is hex-encoded. Output on success: - None -The key-hash can be used to identify which witness signed the log's tree head. -A key-hash, rather than the full verification key, is used to force the verifier -to locate the appropriate key and make an explicit trust decision. +The key-hash can be used to identify which witness signed the log's +tree head. A key-hash, rather than the full verification key, is used +to force the verifier to locate the appropriate key and make an +explicit trust decision. ## Summary of log parameters -- **Public key**: an Ed25519 verification key that can be used to verify the -log's tree head signatures. +- **Public key**: an Ed25519 verification key that can be used to + verify the log's tree head signatures. - **Log identifier**: the hashed public verification key using SHA256. -- **Shard interval**: the time during which the log accepts logging requests. -The shard interval's start and end are inclusive and expressed as the number of -seconds since the UNIX epoch. -- **Base URL**: where the log can be reached over HTTP(S). It is the prefix -before a version-0 specific endpoint. +- **Shard interval**: the time during which the log accepts logging + requests. The shard interval's start and end are inclusive and + expressed as the number of seconds since the UNIX epoch. +- **Base URL**: where the log can be reached over HTTP(S). It is the + prefix before a version-0 specific endpoint. -- cgit v1.2.3 From d13da7fd14c9050a70313f00b71955beb4276132 Mon Sep 17 00:00:00 2001 From: Linus Nordberg Date: Tue, 4 May 2021 16:25:36 +0200 Subject: seconds, not milliseconds --- doc/api.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/doc/api.md b/doc/api.md index 5b7cb19..c9d3db9 100644 --- a/doc/api.md +++ b/doc/api.md @@ -78,7 +78,7 @@ makes a simple format explicit and unambiguous. Tree heads are signed by the log and its witnesses. It contains a timestamp, a tree size, and a root hash. The timestamp is included so that monitors can ensure _liveliness_. It is the time since the UNIX -epoch (January 1, 1970 00:00:00 UTC) in seconds. The tree size +epoch (January 1, 1970 00:00 UTC) in seconds. The tree size specifies the current number of leaves. The root hash fixes the structure and content of the Merkle tree. -- cgit v1.2.3 From 866320e7cb3f8eee21f464cbc56d518f6eb66c72 Mon Sep 17 00:00:00 2001 From: Linus Nordberg Date: Tue, 4 May 2021 16:33:01 +0200 Subject: move long description of sharding to the design doc --- doc/api.md | 49 ++++++++++++++----------------------------------- doc/design.md | 22 ++++++++++++++++++++++ 2 files changed, 36 insertions(+), 35 deletions(-) diff --git a/doc/api.md b/doc/api.md index c9d3db9..3a595ee 100644 --- a/doc/api.md +++ b/doc/api.md @@ -114,41 +114,20 @@ struct tree_leaf { } ``` -Unlike X.509 certificates which already have validity ranges, a -checksum does not carry any such information. Therefore, we require -that the submitter selects a _shard hint_. The selected shard hint -must be in the log's _shard interval_. A shard interval is defined by -a start time and an end time. Both ends of the shard interval are -inclusive and expressed as the number of seconds since the UNIX epoch -(January 1, 1970 00:00 UTC). - -Sharding simplifies log operations because it becomes explicit when a -log can be shutdown. A log must only accept logging requests that -have valid shard hints. A log should only accept logging requests -during the predefined shard interval. Note that _the submitter's -shard hint is not a verified timestamp_. The submitter should set the -shard hint as large as possible. If a roughly verified timestamp is -needed, a cosigned tree head can be used. - -Without a shard hint, the good Samaritan could log all leaves from an -earlier shard into a newer one. Not only would that defeat the -purpose of sharding, but it would also become a potential -denial-of-service vector. - -The signed message is composed of the chosen `shard_hint` and the -submitter's `checksum`. It must be possible to verify -`signature_over_message` using the submitter's public verification -key. - -Note that the way `shard_hint` and `checksum` are serialized with -regards to signing differs from how they're being transmitted to the -log. - -A `key_hash` of the key used for signing `message` is included in -`tree_leaf` so that the leaf can be attributed to the submitter. A -hash, rather than the full public key, is used to motivate the -verifier to locate the appropriate key and make an explicit trust -decision. +`message` is composed of the `shard_hint`, chosen by the submitter to +match the shard interval for the log, and the submitter's `checksum` +to be logged. + +`signature_over_message` is a signature over `message`, using the +submitter's verification key. It must be possible to verify the +signature using the submitter's public verification key, as indicated +by `key_hash`. + +`key_hash` is a hash of the submitter's verification key used for +signing `message`. It is included in `tree_leaf` so that the leaf can +be attributed to the submitter. A hash, rather than the full public +key, is used to motivate verifiers to locate the appropriate key and +make an explicit trust decision. ## Public endpoints Every log has a base URL that identifies it uniquely. The only diff --git a/doc/design.md b/doc/design.md index 4c764e3..a840c01 100644 --- a/doc/design.md +++ b/doc/design.md @@ -216,6 +216,28 @@ additional key-pair. #### What (de)serialization parsers are needed? #### What policy should be used? #### Why witness cosigning? +#### Why sharding? +Unlike X.509 certificates which already have validity ranges, a +checksum does not carry any such information. Therefore, we require +that the submitter selects a _shard hint_. The selected shard hint +must be in the log's _shard interval_. A shard interval is defined by +a start time and an end time. Both ends of the shard interval are +inclusive and expressed as the number of seconds since the UNIX epoch +(January 1, 1970 00:00 UTC). + +Sharding simplifies log operations because it becomes explicit when a +log can be shutdown. A log must only accept logging requests that +have valid shard hints. A log should only accept logging requests +during the predefined shard interval. Note that _the submitter's +shard hint is not a verified timestamp_. The submitter should set the +shard hint as large as possible. If a roughly verified timestamp is +needed, a cosigned tree head can be used. + +Without a shard hint, the good Samaritan could log all leaves from an +earlier shard into a newer one. Not only would that defeat the +purpose of sharding, but it would also become a potential +denial-of-service vector. + #### TODO Add more key questions and answers. - Log spamming -- cgit v1.2.3 From 78c68e528517f157f29784f9dc87b3246f046e52 Mon Sep 17 00:00:00 2001 From: Linus Nordberg Date: Tue, 4 May 2021 16:43:31 +0200 Subject: no need for encoding SPACE --- doc/api.md | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/doc/api.md b/doc/api.md index 3a595ee..d75fe6f 100644 --- a/doc/api.md +++ b/doc/api.md @@ -147,10 +147,10 @@ format as the input data, i.e. as ASCII key/value pairs on the format would send an HTTP message body consisting of `stlog-tree_size: 4711`. The HTTP status code is 200 OK to indicate success. A different HTTP -status code is used to indicate failure. The log should set the -"error" key to a human-readable value that describes what went wrong. -For example, `error=invalid+signature`, `error=rate+limit+exceeded`, -or `error=unknown+leaf+hash`. +status code is used to indicate failure. The log should set the value +value for the key `error` to a human-readable string describing what +went wrong. For example, `error: invalid signature`, `error: rate +limit exceeded`, or `error: unknown leaf hash`. ### get-tree-head-cosigned Returns the latest cosigned tree head. Used together with -- cgit v1.2.3 From ee4ad9e1e4be9e969c13a12f5e76a2b439077b6e Mon Sep 17 00:00:00 2001 From: Linus Nordberg Date: Tue, 4 May 2021 17:19:46 +0200 Subject: another pass over the input and output descriptions Mostly replacing "human-readable" with something more well defined. --- doc/api.md | 132 +++++++++++++++++++++++++++++++------------------------------ 1 file changed, 68 insertions(+), 64 deletions(-) diff --git a/doc/api.md b/doc/api.md index d75fe6f..8a46af6 100644 --- a/doc/api.md +++ b/doc/api.md @@ -237,65 +237,69 @@ There is exactly one `signature` and one `key_hash` field. The ### get-proof-by-hash ``` -POST /st/v0/get-proof-by-hash +GET /st/v0/get-proof-by-hash ``` Input: -- "leaf_hash": a hex-encoded leaf hash that identifies which - `tree_leaf` the log should prove inclusion for. The leaf hash is - computed using the RFC 6962 hashing strategy. In other words, - `SHA256(0x00 | tree_leaf)`. -- "tree_size": a human-readable tree size of the tree head that the - proof should be based on. +- "leaf_hash": leaf identifying which `tree_leaf` the log should prove + inclusion of, hex-encoded. +- "tree_size": tree size of the tree head that the proof should be + based on, as an ASCII-encoded decimal number. Output on success: -- "tree_size": human-readable tree size that the proof is based on. -- "leaf_index": human-readable zero-based index of the leaf that the - proof is based on. -- "inclusion_path": a node hash in hex. +- "tree_size": tree size that the proof is based on, as an + ASCII-encoded decimal number. +- "leaf_index": zero-based index of the leaf that the proof is based + on, as an ASCII-encoded decimal number. +- "inclusion_path": node hash, hex-encoded. -The "inclusion_path" may be omitted or repeated to represent an -inclusion proof of zero or more node hashes. The order of node hashes -follow from our hash strategy, see RFC 6962. +The leaf hash is computed using the RFC 6962 hashing strategy. In +other words, `SHA256(0x00 | tree_leaf)`. + +`inclusion_path` may be omitted or repeated to represent an inclusion +proof of zero or more node hashes. The order of node hashes follow +from the hash strategy, see RFC 6962. ### get-consistency-proof ``` -POST /st/v0/get-consistency-proof +GET /st/v0/get-consistency-proof ``` Input: -- "new_size": human-readable tree size of a newer tree head. -- "old_size": human-readable tree size of an older tree head that the - log should prove is consistent with the newer tree head. +- "new_size": tree size of a newer tree head, as an ASCII-encoded + decimal number. +- "old_size": tree size of an older tree head that the log should + prove is consistent with the newer tree head, as an ASCII-encoded + decimal number. Output on success: -- "new_size": human-readable tree size of a newer tree head that the - proof is based on. -- "old_size": human-readable tree size of an older tree head that the - proof is based on. -- "consistency_path": a node hash in hex. +- "new_size": tree size of the newer tree head that the proof is based + on, as an ASCII-encoded decimal number. +- "old_size": tree size of the older tree head that the proof is based + on, as an ASCII-encoded decimal number. +- "consistency_path": node hash, hex-encoded. -The "consistency_path" may be omitted or repeated to represent a +`consistency_path` may be omitted or repeated to represent a consistency proof of zero or more node hashes. The order of node -hashes follow from our hash strategy, see RFC 6962. +hashes follow from the hash strategy, see RFC 6962. ### get-leaves ``` -POST /st/v0/get-leaves +GET /st/v0/get-leaves ``` Input: -- "start_size": human-readable index of the first leaf to retrieve. -- "end_size": human-readable index of the last leaf to retrieve. +- "start_size": index of the first leaf to retrieve, as an + ASCII-encoded decimal number. +- "end_size": index of the last leaf to retrieve, as an ASCII-encoded + decimal number. Output on success: -- "shard_hint": `tree_leaf.message.shard_hint` as a human-readable - number. -- "checksum": `tree_leaf.message.checksum` in hex. -- "signature_scheme": human-readable number that identifies a - signature scheme. -- "signature": `tree_leaf.signature` in hex. -- "key_hash": `tree_leaf.key_hash` in hex. +- "shard_hint": `tree_leaf.message.shard_hint` as an ASCII-encoded + decimal number. +- "checksum": `tree_leaf.message.checksum`, hex-encoded. +- "signature": `tree_leaf.signature_over_message`, hex-encoded. +- "key_hash": `tree_leaf.key_hash`, hex-encoded. All fields may be repeated to return more than one leaf. The first value in each list refers to the first leaf, the second value in each @@ -307,31 +311,32 @@ must be returned on HTTP status code 200 OK. ### add-leaf ``` -POST /st/v0/add-leaf +GET /st/v0/add-leaf ``` Input: -- "shard_hint": human-readable decimal number in the log's shard - interval that the submitter selected. +- "shard_hint": number within the log's shard interval as an + ASCII-encoded decimal number. - "checksum": the cryptographic checksum that the submitter wants to - log in hex. note: fixed length 64 bytes, validated by the server - somehow -- "signature": the submitter's signature over `tree_leaf.message`. - The result is hex-encoded. + log, hex-encoded. +- "signature_over_message": the submitter's signature over + `tree_leaf.message`, hex-encoded. - "verification_key": the submitter's public verification key. The key is encoded as defined in - [RFC 8032, section 5.1.2](https://tools.ietf.org/html/rfc8032#section-5.1.2). The result is hex-encoded. -- "domain_hint": a domain name that indicates where - `tree_leaf.key_hash` can be retrieved as a DNS TXT resource record - in hex. + [RFC 8032, section 5.1.2](https://tools.ietf.org/html/rfc8032#section-5.1.2) + and then hex-encoded. +- "domain_hint": domain name indicating where `tree_leaf.key_hash` + can be found as a DNS TXT resource record. Output on success: - None -The submitted entry will not be accepted if the signature is invalid -or if the downloaded verification-key hash does not match. The -submitted entry may also not be accepted if the second-level domain -name exceeded its rate limit. By coupling every add-leaf request with +The submission will not be accepted if `signature_over_message` is +invalid or if the key hash retrieved using `domain_hint` does not +match a hash over `verification_key`. + +The submission may also not be accepted if the second-level domain +name exceeded its rate limit. By coupling every add-leaf request to a second-level domain, it becomes more difficult to spam the log. You would need an excessive number of domain names. This becomes costly if free domain names are rejected. @@ -339,31 +344,30 @@ if free domain names are rejected. The log does not publish domain-name to key bindings because key management is more complex than that. -Public logging should not be assumed until an inclusion proof is -available. An inclusion proof should not be relied upon unless it -leads up to a trustworthy signed tree head. Witness cosigning can -make a tree head trustworthy. +Public logging should not be assumed to have happened until an +inclusion proof is available. An inclusion proof should not be relied +upon unless it leads up to a trustworthy signed tree head. Witness +cosigning can make a tree head trustworthy. ### add-cosignature ``` -POST /st/v0/add-cosignature +GET /st/v0/add-cosignature ``` Input: -- "signature": an Ed25519 signature over `tree_head`. The result is - hex-encoded. -- "key_hash": a hash of the witness' public verification key that can - be used to verify the signature. The key is encoded as defined in +- "signature": Ed25519 signature over `tree_head`, hex-encoded. +- "key_hash": hash of the witness' public verification key that can be + used to verify `signature`. The key is encoded as defined in [RFC 8032, section 5.1.2](https://tools.ietf.org/html/rfc8032#section-5.1.2), - and then hashed using SHA256. The hash value is hex-encoded. + and then hashed using SHA256. The hash value is hex-encoded. Output on success: - None -The key-hash can be used to identify which witness signed the log's -tree head. A key-hash, rather than the full verification key, is used -to force the verifier to locate the appropriate key and make an -explicit trust decision. +`key_hash` can be used to identify which witness signed the log's tree +head. A key-hash, rather than the full verification key, is used to +motivate verifiers to locate the appropriate key and make an explicit +trust decision. ## Summary of log parameters - **Public key**: an Ed25519 verification key that can be used to -- cgit v1.2.3 From 8301e63f91b023e57b2d7c8b11d3dff4f0056aed Mon Sep 17 00:00:00 2001 From: Linus Nordberg Date: Tue, 4 May 2021 17:22:06 +0200 Subject: use backticks for quoting single words I think this is more markdownish. --- doc/api.md | 78 +++++++++++++++++++++++++++++++------------------------------- 1 file changed, 39 insertions(+), 39 deletions(-) diff --git a/doc/api.md b/doc/api.md index 8a46af6..c6a4569 100644 --- a/doc/api.md +++ b/doc/api.md @@ -164,19 +164,19 @@ Input: - None Output on success: -- "timestamp": `tree_head.timestamp` ASCII-encoded decimal number, +- `timestamp`: `tree_head.timestamp` ASCII-encoded decimal number, seconds since the UNIX epoch. -- "tree_size": `tree_head.tree_size` ASCII-encoded decimal number. -- "root_hash": `tree_head.root_hash` hex-encoded. -- "signature": hex-encoded Ed25519 signature over `tree_head` +- `tree_size`: `tree_head.tree_size` ASCII-encoded decimal number. +- `root_hash`: `tree_head.root_hash` hex-encoded. +- `signature`: hex-encoded Ed25519 signature over `tree_head` serialzed as described in section `Merkle tree head`. -- "key_hash": a hash of the public verification key (belonging to +- `key_hash`: a hash of the public verification key (belonging to either the log or to one of its witnesses), which can be used to verify the most recent `signature`. The key is encoded as defined in [RFC 8032, section 5.1.2](https://tools.ietf.org/html/rfc8032#section-5.1.2), and then hashed using SHA256. The hash value is hex-encoded. -The "signature" and "key_hash" fields may repeat. The first signature +The `signature` and `key_hash` fields may repeat. The first signature corresponds to the first key hash, the second signature corresponds to the second key hash, etc. The number of signatures and key hashes must match. @@ -193,13 +193,13 @@ Input: - None Output on success: -- "timestamp": `tree_head.timestamp` ASCII-encoded decimal number, +- `timestamp`: `tree_head.timestamp` ASCII-encoded decimal number, seconds since the UNIX epoch. -- "tree_size": `tree_head.tree_size` ASCII-encoded decimal number. -- "root_hash": `tree_head.root_hash` hex-encoded. -- "signature": hex-encoded Ed25519 signature over `tree_head` +- `tree_size`: `tree_head.tree_size` ASCII-encoded decimal number. +- `root_hash`: `tree_head.root_hash` hex-encoded. +- `signature`: hex-encoded Ed25519 signature over `tree_head` serialzed as described in section `Merkle tree head`. -- "key_hash": a hash of the log's public verification key, which can +- `key_hash`: a hash of the log's public verification key, which can be used to verify `signature`. The key is encoded as defined in [RFC 8032, section 5.1.2](https://tools.ietf.org/html/rfc8032#section-5.1.2), and then hashed using SHA256. The hash value is hex-encoded. @@ -220,13 +220,13 @@ Input: - None Output on success: -- "timestamp": `tree_head.timestamp` ASCII-encoded decimal number, +- `timestamp`: `tree_head.timestamp` ASCII-encoded decimal number, seconds since the UNIX epoch. -- "tree_size": `tree_head.tree_size` ASCII-encoded decimal number. -- "root_hash": `tree_head.root_hash` hex-encoded. -- "signature": hex-encoded Ed25519 signature over `tree_head` +- `tree_size`: `tree_head.tree_size` ASCII-encoded decimal number. +- `root_hash`: `tree_head.root_hash` hex-encoded. +- `signature`: hex-encoded Ed25519 signature over `tree_head` serialzed as described in section `Merkle tree head`. -- "key_hash": a hash of the log's public verification key that can be +- `key_hash`: a hash of the log's public verification key that can be used to verify `signature`. The key is encoded as defined in [RFC 8032, section 5.1.2](https://tools.ietf.org/html/rfc8032#section-5.1.2), and then hashed using SHA256. The hash value is hex-encoded. @@ -241,17 +241,17 @@ GET /st/v0/get-proof-by-hash ``` Input: -- "leaf_hash": leaf identifying which `tree_leaf` the log should prove +- `leaf_hash`: leaf identifying which `tree_leaf` the log should prove inclusion of, hex-encoded. -- "tree_size": tree size of the tree head that the proof should be +- `tree_size`: tree size of the tree head that the proof should be based on, as an ASCII-encoded decimal number. Output on success: -- "tree_size": tree size that the proof is based on, as an +- `tree_size`: tree size that the proof is based on, as an ASCII-encoded decimal number. -- "leaf_index": zero-based index of the leaf that the proof is based +- `leaf_index`: zero-based index of the leaf that the proof is based on, as an ASCII-encoded decimal number. -- "inclusion_path": node hash, hex-encoded. +- `inclusion_path`: node hash, hex-encoded. The leaf hash is computed using the RFC 6962 hashing strategy. In other words, `SHA256(0x00 | tree_leaf)`. @@ -266,18 +266,18 @@ GET /st/v0/get-consistency-proof ``` Input: -- "new_size": tree size of a newer tree head, as an ASCII-encoded +- `new_size`: tree size of a newer tree head, as an ASCII-encoded decimal number. -- "old_size": tree size of an older tree head that the log should +- `old_size`: tree size of an older tree head that the log should prove is consistent with the newer tree head, as an ASCII-encoded decimal number. Output on success: -- "new_size": tree size of the newer tree head that the proof is based +- `new_size`: tree size of the newer tree head that the proof is based on, as an ASCII-encoded decimal number. -- "old_size": tree size of the older tree head that the proof is based +- `old_size`: tree size of the older tree head that the proof is based on, as an ASCII-encoded decimal number. -- "consistency_path": node hash, hex-encoded. +- `consistency_path`: node hash, hex-encoded. `consistency_path` may be omitted or repeated to represent a consistency proof of zero or more node hashes. The order of node @@ -289,17 +289,17 @@ GET /st/v0/get-leaves ``` Input: -- "start_size": index of the first leaf to retrieve, as an +- `start_size`: index of the first leaf to retrieve, as an ASCII-encoded decimal number. -- "end_size": index of the last leaf to retrieve, as an ASCII-encoded +- `end_size`: index of the last leaf to retrieve, as an ASCII-encoded decimal number. Output on success: -- "shard_hint": `tree_leaf.message.shard_hint` as an ASCII-encoded +- `shard_hint`: `tree_leaf.message.shard_hint` as an ASCII-encoded decimal number. -- "checksum": `tree_leaf.message.checksum`, hex-encoded. -- "signature": `tree_leaf.signature_over_message`, hex-encoded. -- "key_hash": `tree_leaf.key_hash`, hex-encoded. +- `checksum`: `tree_leaf.message.checksum`, hex-encoded. +- `signature`: `tree_leaf.signature_over_message`, hex-encoded. +- `key_hash`: `tree_leaf.key_hash`, hex-encoded. All fields may be repeated to return more than one leaf. The first value in each list refers to the first leaf, the second value in each @@ -315,17 +315,17 @@ GET /st/v0/add-leaf ``` Input: -- "shard_hint": number within the log's shard interval as an +- `shard_hint`: number within the log's shard interval as an ASCII-encoded decimal number. -- "checksum": the cryptographic checksum that the submitter wants to +- `checksum`: the cryptographic checksum that the submitter wants to log, hex-encoded. -- "signature_over_message": the submitter's signature over +- `signature_over_message`: the submitter's signature over `tree_leaf.message`, hex-encoded. -- "verification_key": the submitter's public verification key. The +- `verification_key`: the submitter's public verification key. The key is encoded as defined in [RFC 8032, section 5.1.2](https://tools.ietf.org/html/rfc8032#section-5.1.2) and then hex-encoded. -- "domain_hint": domain name indicating where `tree_leaf.key_hash` +- `domain_hint`: domain name indicating where `tree_leaf.key_hash` can be found as a DNS TXT resource record. Output on success: @@ -355,8 +355,8 @@ GET /st/v0/add-cosignature ``` Input: -- "signature": Ed25519 signature over `tree_head`, hex-encoded. -- "key_hash": hash of the witness' public verification key that can be +- `signature`: Ed25519 signature over `tree_head`, hex-encoded. +- `key_hash`: hash of the witness' public verification key that can be used to verify `signature`. The key is encoded as defined in [RFC 8032, section 5.1.2](https://tools.ietf.org/html/rfc8032#section-5.1.2), and then hashed using SHA256. The hash value is hex-encoded. -- cgit v1.2.3 From 8261776989fd25fbdcf1f0e930c1b3848886ba70 Mon Sep 17 00:00:00 2001 From: Linus Nordberg Date: Wed, 5 May 2021 10:09:35 +0200 Subject: minor wording --- doc/design.md | 58 +++++++++++++++++++++++++++++----------------------------- 1 file changed, 29 insertions(+), 29 deletions(-) diff --git a/doc/design.md b/doc/design.md index a840c01..a1a6140 100644 --- a/doc/design.md +++ b/doc/design.md @@ -74,46 +74,46 @@ our design is that this additional tooling makes no outbound connections. The above data flows are thus preserved. ### A bird's view -A central part of any transparency log is the data. The data is stored by the +A central part of any transparency log is the data stored by the log. The data is stored by the leaves of an append-only Merkle tree. Our leaf structure contains four fields: - **shard_hint**: a number that binds the leaf to a particular _shard interval_. Sharding means that the log has a predefined time during which logging requests -will be accepted. Once elapsed, the log can be shut down. +are accepted. Once elapsed, the log can be shut down. - **checksum**: a cryptographic hash of some opaque data. The log never -sees the opaque data; just the hash. +sees the opaque data; just the hash made by the data publisher. - **signature**: a digital signature that is computed by the data publisher over the leaf's shard hint and checksum. -- **key_hash**: a cryptographic hash of the public verification key that can be -used to verify the leaf's signature. +- **key_hash**: a cryptographic hash of the data publisher's public verification key that can be +used to verify the signature. #### Step 1 - preparing a logging request The data publisher selects a shard hint and a checksum that should be logged. For example, the shard hint could be "logs that are active during 2021". The -checksum might be a hashed release file or something else. +checksum might be the hash of a release file. -The data publisher signs the selected shard hint and checksum using their secret +The data publisher signs the selected shard hint and checksum using a secret signing key. Both the signed message and the signature is stored in the leaf for anyone to verify. Including a shard hint in the signed message -ensures that the good Samaritan cannot change it to log all leaves from an +ensures that a good Samaritan cannot change it to log all leaves from an earlier shard into a newer one. -The hashed public verification key is also stored in the leaf. This makes it -easy to attribute the leaf to the signing entity. For example, a data publisher +A hash of the public verification key is also stored in the leaf. This makes it +possible to attribute the leaf to the data publisher. For example, a data publisher that monitors the log can look for leaves that match their own key hash(es). -A hash, rather than the full public verification key, is used to force the -verifier to locate the key and trust it explicitly. Not disclosing the public -verification key in the leaf makes it more difficult to use an untrusted key _by +A hash, rather than the full public verification key, is used to motivate the +verifier to locate the key and make an explicit trust decision. Not disclosing the public +verification key in the leaf makes it more unlikely that someone would use an untrusted key _by mistake_. #### Step 2 - submitting a logging request The log implements an HTTP(S) API. Input and output is human-readable and uses a simple key-value format. A more complex parser like JSON is not needed -because the exchanged data structures are basic enough. +because the exchanged data structures are primitive enough. The data publisher submits their shard hint, checksum, signature, and public verification key as key-value pairs. The log will use the public verification -key to check that the signature is valid, then hash it to construct the leaf. +key to check that the signature is valid, then hash it to construct the `key_hash` part of the leaf. The data publisher also submits a _domain hint_. The log will download a DNS TXT resource record based on the provided domain name. The downloaded result @@ -126,8 +126,8 @@ Using DNS to combat spam is convenient because many data publishers already have a domain name. A single domain name is also relatively cheap. Another benefit is that the same anti-spam mechanism can be used across several independent logs without coordination. This is important because a healthy log -ecosystem needs more than one log to be reliable. DNS also has built-in -caching that can be influenced by setting TTLs accordingly. +ecosystem needs more than one log in order to be reliable. DNS also has built-in +caching which data publishers can influence by setting TTLs accordingly. The submitter's domain hint is not part of the leaf because key management is more complex than that. A separate project should focus on transparent key @@ -136,26 +136,26 @@ management. The scope of our work is transparent _key-usage_. The log will _try_ to incorporate a leaf into the Merkle tree if a logging request is accepted. There are no _promises of public logging_ as in Certificate Transparency. Therefore, the submitter needs to wait for an -inclusion proof before concluding that the request succeeded. Not having +inclusion proof to appear before concluding that the logging request succeeded. Not having inclusion promises makes the log less complex. #### Step 3 - distributing proofs of public logging The data publisher is responsible for collecting all cryptographic proofs that their end-users will need to enforce public logging. The collection below -should be downloadable from the same place that the data is normally hosted. +should be downloadable from the same place that published data is normally hosted. 1. **Opaque data**: the data publisher's opaque data. 2. **Shard hint**: the data publisher's selected shard hint. 3. **Signature**: the data publisher's leaf signature. 4. **Cosigned tree head**: the log's tree head and a _list of signatures_ that state it is consistent with prior history. -5. **Inclusion proof**: a proof of inclusion that is based on the leaf and tree +5. **Inclusion proof**: a proof of inclusion based on the logged leaf and tree head in question. -The public verification key is known. Therefore, the first three fields are +The data publisher's public verification key is known. Therefore, the first three fields are sufficient to reconstruct the logged leaf. The leaf's signature can be verified. The final two fields then prove that the leaf is in the log. If the leaf is included in the log, any monitor can detect that there is a new -signature for a data publisher's public verification key. +signature made by a given data publisher, 's public verification key. The catch is that the proof of logging is only as convincing as the tree head that the inclusion proof leads up to. To bypass public logging, the attacker @@ -191,7 +191,7 @@ signature tools like `gpg`, `ssh-keygen -Y`, and `signify` cannot verify proofs of public logging. Therefore, _additional tooling must already be installed by end-users_. That tooling should verify hashes using the log's hash function. That tooling should also verify signatures using the log's signature scheme. -Signed messages include tree heads as well as tree leaves. +Both tree heads and tree leaves are being signed. #### Why not let the data publisher pick their own signature scheme and format? Agility introduces complexity and difficult policy questions. For example, @@ -202,13 +202,13 @@ There is not much we can do if a data publisher _refuses_ to rely on the log's hash function or signature scheme. #### What if the data publisher must use a specific signature scheme or format? -You may _cross-sign_ the data as follows. -1. Sign the opaque data as you normally would. -2. Hash the opaque data and use that as the leaf's checksum. Sign the leaf -using the log's signature scheme. +They may _cross-sign_ the data as follows. +1. Sign the data as they're used to. +2. Hash the data and use the result as the leaf's checksum to be logged. +3. Sign the leaf using the log's signature scheme. -First the end-user verifies that the normal signature is valid. Then the -end-user lets the additional tooling (that is already required) verify the rest. +For verification, the end-user first verifies that the usual signature from step 1 is valid. Then the +end-user uses the additional tooling (which is already required) to verify the rest. Cross-signing should be a relatively comfortable upgrade path that is backwards compatible. The downside is that the data publisher may need to manage an additional key-pair. -- cgit v1.2.3 From cd02e6e2bd7e36d8333824e57913d08a56d8a85b Mon Sep 17 00:00:00 2001 From: Linus Nordberg Date: Wed, 5 May 2021 12:31:04 +0200 Subject: add reminder about another q/a --- doc/design.md | 1 + 1 file changed, 1 insertion(+) diff --git a/doc/design.md b/doc/design.md index a1a6140..2e01a34 100644 --- a/doc/design.md +++ b/doc/design.md @@ -245,6 +245,7 @@ Add more key questions and answers. - Why we removed identifier field from the leaf - Explain `latest`, `stable` and `cosigned` tree head. - Privacy aspects +- How does this whole thing work with more than one log? ## Concluding remarks Example of binary transparency and reproducible builds. -- cgit v1.2.3 From c4a99d20dcbf524f94a018ac712d830e7e655ce2 Mon Sep 17 00:00:00 2001 From: Rasmus Dahlberg Date: Tue, 11 May 2021 11:28:01 +0200 Subject: removed unused schemas --- doc/schema/consistency_proof.schema.json | 30 - doc/schema/example/consistency_proof.json | 7 - doc/schema/example/inclusion_proof.json | 7 - doc/schema/example/leaves.json | 14 - doc/schema/example/sth.json | 11 - doc/schema/inclusion_proof.schema.json | 30 - doc/schema/leaves.schema.json | 38 - doc/schema/sth.schema.json | 50 - trunnel/README.md | 9 - trunnel/stfe.c | 3580 ----------------------------- trunnel/stfe.h | 1235 ---------- trunnel/stfe.trunnel | 94 - 12 files changed, 5105 deletions(-) delete mode 100644 doc/schema/consistency_proof.schema.json delete mode 100644 doc/schema/example/consistency_proof.json delete mode 100644 doc/schema/example/inclusion_proof.json delete mode 100644 doc/schema/example/leaves.json delete mode 100644 doc/schema/example/sth.json delete mode 100644 doc/schema/inclusion_proof.schema.json delete mode 100644 doc/schema/leaves.schema.json delete mode 100644 doc/schema/sth.schema.json delete mode 100644 trunnel/README.md delete mode 100644 trunnel/stfe.c delete mode 100644 trunnel/stfe.h delete mode 100644 trunnel/stfe.trunnel diff --git a/doc/schema/consistency_proof.schema.json b/doc/schema/consistency_proof.schema.json deleted file mode 100644 index 003f3c7..0000000 --- a/doc/schema/consistency_proof.schema.json +++ /dev/null @@ -1,30 +0,0 @@ -{ - "$schema": "https://json-schema.org/draft-07/schema#", - "title": "inclusion_proof", - "description": "JSON-formatted inclusion proof, version 0.", - - "type": "object", - "required": [ "new_size", "old_size", "consistency_proof" ], - "properties": { - "new_size": { - "description": "The tree size of the newer Merkle tree head.", - "type": "integer", - "minimum": 0 - }, - "old_size": { - "description": "The tree size of the older Merkle tree head.", - "type": "integer", - "minimum": 0 - }, - "consistency_proof": { - "description": "A list of base64-encoded node hashes that proves consistency", - "type": "array", - "items": { - "description": "A node hash in base64", - "type": "string", - "minLength": 44, - "maxLength": 44 - } - } - } -} diff --git a/doc/schema/example/consistency_proof.json b/doc/schema/example/consistency_proof.json deleted file mode 100644 index 0a323b7..0000000 --- a/doc/schema/example/consistency_proof.json +++ /dev/null @@ -1,7 +0,0 @@ -{ - "new_size": 2, - "old_size": 1, - "consistency_proof": [ - "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA=" - ] -} diff --git a/doc/schema/example/inclusion_proof.json b/doc/schema/example/inclusion_proof.json deleted file mode 100644 index d46d426..0000000 --- a/doc/schema/example/inclusion_proof.json +++ /dev/null @@ -1,7 +0,0 @@ -{ - "tree_size": 2, - "leaf_index": 0, - "inclusion_proof": [ - "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA=" - ] -} diff --git a/doc/schema/example/leaves.json b/doc/schema/example/leaves.json deleted file mode 100644 index 1eed05d..0000000 --- a/doc/schema/example/leaves.json +++ /dev/null @@ -1,14 +0,0 @@ -[ - { - "checksum": "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA=", - "signature_scheme": 1, - "signature": "CCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCC=", - "key_hash": "DDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDD=" - }, - { - "checksum": "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA=", - "signature_scheme": 2, - "signature": "BBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBB=", - "key_hash": "CCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCC=" - } -] diff --git a/doc/schema/example/sth.json b/doc/schema/example/sth.json deleted file mode 100644 index ec3ad11..0000000 --- a/doc/schema/example/sth.json +++ /dev/null @@ -1,11 +0,0 @@ -{ - "timestamp": 0, - "tree_size": 0, - "root_hash": "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA=", - "signatures": [ - { - "key_hash": "BBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBB=", - "signature": "CCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCC=" - } - ] -} diff --git a/doc/schema/inclusion_proof.schema.json b/doc/schema/inclusion_proof.schema.json deleted file mode 100644 index 3309d37..0000000 --- a/doc/schema/inclusion_proof.schema.json +++ /dev/null @@ -1,30 +0,0 @@ -{ - "$schema": "https://json-schema.org/draft-07/schema#", - "title": "inclusion_proof", - "description": "JSON-formatted inclusion proof, version 0.", - - "type": "object", - "required": [ "tree_size", "leaf_index", "inclusion_proof" ], - "properties": { - "tree_size": { - "description": "The Merkle tree size that the inclusion proof is based on.", - "type": "integer", - "minimum": 0 - }, - "leaf_index": { - "description": "The zero-based index of the leaf that the inclusion proof is for.", - "type": "integer", - "minimum": 0 - }, - "inclusion_proof": { - "description": "A list of base64-encoded node hashes that proves inclusion", - "type": "array", - "items": { - "description": "A node hash in base64", - "type": "string", - "minLength": 44, - "maxLength": 44 - } - } - } -} diff --git a/doc/schema/leaves.schema.json b/doc/schema/leaves.schema.json deleted file mode 100644 index 74d7454..0000000 --- a/doc/schema/leaves.schema.json +++ /dev/null @@ -1,38 +0,0 @@ -{ - "$schema": "https://json-schema.org/draft-07/schema#", - "title": "list of tree_leaf", - "description": "JSON-formatted tree leaf list, version 0.", - - "type": "array", - "description": "A list Merkle tree leaves", - "items": { - "type": "object", - "required": [ "checksum", "signature_scheme", "signature", "key_hash" ], - "properties": { - "checksum": { - "description": "A cryptographic hash that is computed over some data of opaque type. The result is base64-encoded.", - "type": "string", - "minLength": 44, - "maxLength": 44 - }, - "signature_scheme": { - "description": "An integer that identifies the signature scheme used by the submitter. See API documentation.", - "type": "integer", - "enum": [ 1, 2, 3 ] - }, - "signature": { - "description": "The submitter's signature over the checksum in base64", - "type": "string", - "minLength": 44, - "maxLength": 684 - }, - "key_hash": { - "description": "A public verification-key hash that identifies the signer.", - "type": "string", - "minLength": 44, - "maxLength": 44 - } - } - }, - "minItems": 1 -} diff --git a/doc/schema/sth.schema.json b/doc/schema/sth.schema.json deleted file mode 100644 index 86de2d3..0000000 --- a/doc/schema/sth.schema.json +++ /dev/null @@ -1,50 +0,0 @@ -{ - "$schema": "https://json-schema.org/draft-07/schema#", - "title": "signed_tree_head_v0", - "description": "JSON-formatted signed tree head, version 0.", - - "type": "object", - "required": [ "timestamp", "tree_size", "root_hash", "signatures" ], - "properties": { - "timestamp": { - "description": "The number of milliseconds since the UNIX epoch (January 1, 1970 00:00:00 UTC).", - "type": "integer", - "minimum": 0 - }, - "tree_size": { - "description": "The number of entries that are stored in the log's Merkle tree.", - "type": "integer", - "minimum": 0 - }, - "root_hash": { - "description": "The log's Merkle tree root hash in base64.", - "type": "string", - "minLength": 44, - "maxLength": 44 - }, - "signatures": { - "description": "A list of signer-signature pairs.", - "type": "array", - "items": { - "description": "A signer-signature pair.", - "type": "object", - "required": [ "key_hash", "signature" ], - "properties": { - "key_hash": { - "description": "A public verification-key hash that identifies the signer in base64.", - "type": "string", - "minLength": 44, - "maxLength": 44 - }, - "signature": { - "description": "The signer's signature over the log's tree_leaf structure in base64.", - "type": "string", - "minLength": 44, - "maxLength": 44 - } - } - }, - "minItems": 1 - } - } -} diff --git a/trunnel/README.md b/trunnel/README.md deleted file mode 100644 index 74180a0..0000000 --- a/trunnel/README.md +++ /dev/null @@ -1,9 +0,0 @@ -Expressing doc/formats.md in trunnel[0]. - -- stfe.trunnel is the input file -- stfe.[ch] are output files from running trunnel on stfe.trunnel. - -TODO: Generate go code using [1]. - -[0] https://gitweb.torproject.org/trunnel.git -[1] https://github.com/mmcloughlin/trunnel diff --git a/trunnel/stfe.c b/trunnel/stfe.c deleted file mode 100644 index 7bf2652..0000000 --- a/trunnel/stfe.c +++ /dev/null @@ -1,3580 +0,0 @@ -/* stfe.c -- generated by Trunnel v1.5.3. - * https://gitweb.torproject.org/trunnel.git - * You probably shouldn't edit this file. - */ -#include -#include "trunnel-impl.h" - -#include "stfe.h" - -#define TRUNNEL_SET_ERROR_CODE(obj) \ - do { \ - (obj)->trunnel_error_code_ = 1; \ - } while (0) - -#if defined(__COVERITY__) || defined(__clang_analyzer__) -/* If we're running a static analysis tool, we don't want it to complain - * that some of our remaining-bytes checks are dead-code. */ -int stfe_deadcode_dummy__ = 0; -#define OR_DEADCODE_DUMMY || stfe_deadcode_dummy__ -#else -#define OR_DEADCODE_DUMMY -#endif - -#define CHECK_REMAINING(nbytes, label) \ - do { \ - if (remaining < (nbytes) OR_DEADCODE_DUMMY) { \ - goto label; \ - } \ - } while (0) - -hash_t * -hash_new(void) -{ - hash_t *val = trunnel_calloc(1, sizeof(hash_t)); - if (NULL == val) - return NULL; - return val; -} - -/** Release all storage held inside 'obj', but do not free 'obj'. - */ -static void -hash_clear(hash_t *obj) -{ - (void) obj; -} - -void -hash_free(hash_t *obj) -{ - if (obj == NULL) - return; - hash_clear(obj); - trunnel_memwipe(obj, sizeof(hash_t)); - trunnel_free_(obj); -} - -size_t -hash_getlen_hash(const hash_t *inp) -{ - (void)inp; return 32; -} - -uint8_t -hash_get_hash(hash_t *inp, size_t idx) -{ - trunnel_assert(idx < 32); - return inp->hash[idx]; -} - -uint8_t -hash_getconst_hash(const hash_t *inp, size_t idx) -{ - return hash_get_hash((hash_t*)inp, idx); -} -int -hash_set_hash(hash_t *inp, size_t idx, uint8_t elt) -{ - trunnel_assert(idx < 32); - inp->hash[idx] = elt; - return 0; -} - -uint8_t * -hash_getarray_hash(hash_t *inp) -{ - return inp->hash; -} -const uint8_t * -hash_getconstarray_hash(const hash_t *inp) -{ - return (const uint8_t *)hash_getarray_hash((hash_t*)inp); -} -const char * -hash_check(const hash_t *obj) -{ - if (obj == NULL) - return "Object was NULL"; - if (obj->trunnel_error_code_) - return "A set function failed on this object"; - return NULL; -} - -ssize_t -hash_encoded_len(const hash_t *obj) -{ - ssize_t result = 0; - - if (NULL != hash_check(obj)) - return -1; - - - /* Length of u8 hash[32] */ - result += 32; - return result; -} -int -hash_clear_errors(hash_t *obj) -{ - int r = obj->trunnel_error_code_; - obj->trunnel_error_code_ = 0; - return r; -} -ssize_t -hash_encode(uint8_t *output, const size_t avail, const hash_t *obj) -{ - ssize_t result = 0; - size_t written = 0; - uint8_t *ptr = output; - const char *msg; -#ifdef TRUNNEL_CHECK_ENCODED_LEN - const ssize_t encoded_len = hash_encoded_len(obj); -#endif - - if (NULL != (msg = hash_check(obj))) - goto check_failed; - -#ifdef TRUNNEL_CHECK_ENCODED_LEN - trunnel_assert(encoded_len >= 0); -#endif - - /* Encode u8 hash[32] */ - trunnel_assert(written <= avail); - if (avail - written < 32) - goto truncated; - memcpy(ptr, obj->hash, 32); - written += 32; ptr += 32; - - - trunnel_assert(ptr == output + written); -#ifdef TRUNNEL_CHECK_ENCODED_LEN - { - trunnel_assert(encoded_len >= 0); - trunnel_assert((size_t)encoded_len == written); - } - -#endif - - return written; - - truncated: - result = -2; - goto fail; - check_failed: - (void)msg; - result = -1; - goto fail; - fail: - trunnel_assert(result < 0); - return result; -} - -/** As hash_parse(), but do not allocate the output object. - */ -static ssize_t -hash_parse_into(hash_t *obj, const uint8_t *input, const size_t len_in) -{ - const uint8_t *ptr = input; - size_t remaining = len_in; - ssize_t result = 0; - (void)result; - - /* Parse u8 hash[32] */ - CHECK_REMAINING(32, truncated); - memcpy(obj->hash, ptr, 32); - remaining -= 32; ptr += 32; - trunnel_assert(ptr + remaining == input + len_in); - return len_in - remaining; - - truncated: - return -2; -} - -ssize_t -hash_parse(hash_t **output, const uint8_t *input, const size_t len_in) -{ - ssize_t result; - *output = hash_new(); - if (NULL == *output) - return -1; - result = hash_parse_into(*output, input, len_in); - if (result < 0) { - hash_free(*output); - *output = NULL; - } - return result; -} -req_get_consistency_proof_v1_t * -req_get_consistency_proof_v1_new(void) -{ - req_get_consistency_proof_v1_t *val = trunnel_calloc(1, sizeof(req_get_consistency_proof_v1_t)); - if (NULL == val) - return NULL; - return val; -} - -/** Release all storage held inside 'obj', but do not free 'obj'. - */ -static void -req_get_consistency_proof_v1_clear(req_get_consistency_proof_v1_t *obj) -{ - (void) obj; -} - -void -req_get_consistency_proof_v1_free(req_get_consistency_proof_v1_t *obj) -{ - if (obj == NULL) - return; - req_get_consistency_proof_v1_clear(obj); - trunnel_memwipe(obj, sizeof(req_get_consistency_proof_v1_t)); - trunnel_free_(obj); -} - -uint64_t -req_get_consistency_proof_v1_get_old_size(const req_get_consistency_proof_v1_t *inp) -{ - return inp->old_size; -} -int -req_get_consistency_proof_v1_set_old_size(req_get_consistency_proof_v1_t *inp, uint64_t val) -{ - inp->old_size = val; - return 0; -} -uint64_t -req_get_consistency_proof_v1_get_new_size(const req_get_consistency_proof_v1_t *inp) -{ - return inp->new_size; -} -int -req_get_consistency_proof_v1_set_new_size(req_get_consistency_proof_v1_t *inp, uint64_t val) -{ - inp->new_size = val; - return 0; -} -const char * -req_get_consistency_proof_v1_check(const req_get_consistency_proof_v1_t *obj) -{ - if (obj == NULL) - return "Object was NULL"; - if (obj->trunnel_error_code_) - return "A set function failed on this object"; - return NULL; -} - -ssize_t -req_get_consistency_proof_v1_encoded_len(const req_get_consistency_proof_v1_t *obj) -{ - ssize_t result = 0; - - if (NULL != req_get_consistency_proof_v1_check(obj)) - return -1; - - - /* Length of u64 old_size */ - result += 8; - - /* Length of u64 new_size */ - result += 8; - return result; -} -int -req_get_consistency_proof_v1_clear_errors(req_get_consistency_proof_v1_t *obj) -{ - int r = obj->trunnel_error_code_; - obj->trunnel_error_code_ = 0; - return r; -} -ssize_t -req_get_consistency_proof_v1_encode(uint8_t *output, const size_t avail, const req_get_consistency_proof_v1_t *obj) -{ - ssize_t result = 0; - size_t written = 0; - uint8_t *ptr = output; - const char *msg; -#ifdef TRUNNEL_CHECK_ENCODED_LEN - const ssize_t encoded_len = req_get_consistency_proof_v1_encoded_len(obj); -#endif - - if (NULL != (msg = req_get_consistency_proof_v1_check(obj))) - goto check_failed; - -#ifdef TRUNNEL_CHECK_ENCODED_LEN - trunnel_assert(encoded_len >= 0); -#endif - - /* Encode u64 old_size */ - trunnel_assert(written <= avail); - if (avail - written < 8) - goto truncated; - trunnel_set_uint64(ptr, trunnel_htonll(obj->old_size)); - written += 8; ptr += 8; - - /* Encode u64 new_size */ - trunnel_assert(written <= avail); - if (avail - written < 8) - goto truncated; - trunnel_set_uint64(ptr, trunnel_htonll(obj->new_size)); - written += 8; ptr += 8; - - - trunnel_assert(ptr == output + written); -#ifdef TRUNNEL_CHECK_ENCODED_LEN - { - trunnel_assert(encoded_len >= 0); - trunnel_assert((size_t)encoded_len == written); - } - -#endif - - return written; - - truncated: - result = -2; - goto fail; - check_failed: - (void)msg; - result = -1; - goto fail; - fail: - trunnel_assert(result < 0); - return result; -} - -/** As req_get_consistency_proof_v1_parse(), but do not allocate the - * output object. - */ -static ssize_t -req_get_consistency_proof_v1_parse_into(req_get_consistency_proof_v1_t *obj, const uint8_t *input, const size_t len_in) -{ - const uint8_t *ptr = input; - size_t remaining = len_in; - ssize_t result = 0; - (void)result; - - /* Parse u64 old_size */ - CHECK_REMAINING(8, truncated); - obj->old_size = trunnel_ntohll(trunnel_get_uint64(ptr)); - remaining -= 8; ptr += 8; - - /* Parse u64 new_size */ - CHECK_REMAINING(8, truncated); - obj->new_size = trunnel_ntohll(trunnel_get_uint64(ptr)); - remaining -= 8; ptr += 8; - trunnel_assert(ptr + remaining == input + len_in); - return len_in - remaining; - - truncated: - return -2; -} - -ssize_t -req_get_consistency_proof_v1_parse(req_get_consistency_proof_v1_t **output, const uint8_t *input, const size_t len_in) -{ - ssize_t result; - *output = req_get_consistency_proof_v1_new(); - if (NULL == *output) - return -1; - result = req_get_consistency_proof_v1_parse_into(*output, input, len_in); - if (result < 0) { - req_get_consistency_proof_v1_free(*output); - *output = NULL; - } - return result; -} -req_get_entries_v1_t * -req_get_entries_v1_new(void) -{ - req_get_entries_v1_t *val = trunnel_calloc(1, sizeof(req_get_entries_v1_t)); - if (NULL == val) - return NULL; - return val; -} - -/** Release all storage held inside 'obj', but do not free 'obj'. - */ -static void -req_get_entries_v1_clear(req_get_entries_v1_t *obj) -{ - (void) obj; -} - -void -req_get_entries_v1_free(req_get_entries_v1_t *obj) -{ - if (obj == NULL) - return; - req_get_entries_v1_clear(obj); - trunnel_memwipe(obj, sizeof(req_get_entries_v1_t)); - trunnel_free_(obj); -} - -uint64_t -req_get_entries_v1_get_start_size(const req_get_entries_v1_t *inp) -{ - return inp->start_size; -} -int -req_get_entries_v1_set_start_size(req_get_entries_v1_t *inp, uint64_t val) -{ - inp->start_size = val; - return 0; -} -uint64_t -req_get_entries_v1_get_end_size(const req_get_entries_v1_t *inp) -{ - return inp->end_size; -} -int -req_get_entries_v1_set_end_size(req_get_entries_v1_t *inp, uint64_t val) -{ - inp->end_size = val; - return 0; -} -const char * -req_get_entries_v1_check(const req_get_entries_v1_t *obj) -{ - if (obj == NULL) - return "Object was NULL"; - if (obj->trunnel_error_code_) - return "A set function failed on this object"; - return NULL; -} - -ssize_t -req_get_entries_v1_encoded_len(const req_get_entries_v1_t *obj) -{ - ssize_t result = 0; - - if (NULL != req_get_entries_v1_check(obj)) - return -1; - - - /* Length of u64 start_size */ - result += 8; - - /* Length of u64 end_size */ - result += 8; - return result; -} -int -req_get_entries_v1_clear_errors(req_get_entries_v1_t *obj) -{ - int r = obj->trunnel_error_code_; - obj->trunnel_error_code_ = 0; - return r; -} -ssize_t -req_get_entries_v1_encode(uint8_t *output, const size_t avail, const req_get_entries_v1_t *obj) -{ - ssize_t result = 0; - size_t written = 0; - uint8_t *ptr = output; - const char *msg; -#ifdef TRUNNEL_CHECK_ENCODED_LEN - const ssize_t encoded_len = req_get_entries_v1_encoded_len(obj); -#endif - - if (NULL != (msg = req_get_entries_v1_check(obj))) - goto check_failed; - -#ifdef TRUNNEL_CHECK_ENCODED_LEN - trunnel_assert(encoded_len >= 0); -#endif - - /* Encode u64 start_size */ - trunnel_assert(written <= avail); - if (avail - written < 8) - goto truncated; - trunnel_set_uint64(ptr, trunnel_htonll(obj->start_size)); - written += 8; ptr += 8; - - /* Encode u64 end_size */ - trunnel_assert(written <= avail); - if (avail - written < 8) - goto truncated; - trunnel_set_uint64(ptr, trunnel_htonll(obj->end_size)); - written += 8; ptr += 8; - - - trunnel_assert(ptr == output + written); -#ifdef TRUNNEL_CHECK_ENCODED_LEN - { - trunnel_assert(encoded_len >= 0); - trunnel_assert((size_t)encoded_len == written); - } - -#endif - - return written; - - truncated: - result = -2; - goto fail; - check_failed: - (void)msg; - result = -1; - goto fail; - fail: - trunnel_assert(result < 0); - return result; -} - -/** As req_get_entries_v1_parse(), but do not allocate the output - * object. - */ -static ssize_t -req_get_entries_v1_parse_into(req_get_entries_v1_t *obj, const uint8_t *input, const size_t len_in) -{ - const uint8_t *ptr = input; - size_t remaining = len_in; - ssize_t result = 0; - (void)result; - - /* Parse u64 start_size */ - CHECK_REMAINING(8, truncated); - obj->start_size = trunnel_ntohll(trunnel_get_uint64(ptr)); - remaining -= 8; ptr += 8; - - /* Parse u64 end_size */ - CHECK_REMAINING(8, truncated); - obj->end_size = trunnel_ntohll(trunnel_get_uint64(ptr)); - remaining -= 8; ptr += 8; - trunnel_assert(ptr + remaining == input + len_in); - return len_in - remaining; - - truncated: - return -2; -} - -ssize_t -req_get_entries_v1_parse(req_get_entries_v1_t **output, const uint8_t *input, const size_t len_in) -{ - ssize_t result; - *output = req_get_entries_v1_new(); - if (NULL == *output) - return -1; - result = req_get_entries_v1_parse_into(*output, input, len_in); - if (result < 0) { - req_get_entries_v1_free(*output); - *output = NULL; - } - return result; -} -req_get_proof_by_hash_v1_t * -req_get_proof_by_hash_v1_new(void) -{ - req_get_proof_by_hash_v1_t *val = trunnel_calloc(1, sizeof(req_get_proof_by_hash_v1_t)); - if (NULL == val) - return NULL; - return val; -} - -/** Release all storage held inside 'obj', but do not free 'obj'. - */ -static void -req_get_proof_by_hash_v1_clear(req_get_proof_by_hash_v1_t *obj) -{ - (void) obj; -} - -void -req_get_proof_by_hash_v1_free(req_get_proof_by_hash_v1_t *obj) -{ - if (obj == NULL) - return; - req_get_proof_by_hash_v1_clear(obj); - trunnel_memwipe(obj, sizeof(req_get_proof_by_hash_v1_t)); - trunnel_free_(obj); -} - -uint64_t -req_get_proof_by_hash_v1_get_tree_size(const req_get_proof_by_hash_v1_t *inp) -{ - return inp->tree_size; -} -int -req_get_proof_by_hash_v1_set_tree_size(req_get_proof_by_hash_v1_t *inp, uint64_t val) -{ - inp->tree_size = val; - return 0; -} -size_t -req_get_proof_by_hash_v1_getlen_leaf_hash(const req_get_proof_by_hash_v1_t *inp) -{ - (void)inp; return 32; -} - -uint8_t -req_get_proof_by_hash_v1_get_leaf_hash(req_get_proof_by_hash_v1_t *inp, size_t idx) -{ - trunnel_assert(idx < 32); - return inp->leaf_hash[idx]; -} - -uint8_t -req_get_proof_by_hash_v1_getconst_leaf_hash(const req_get_proof_by_hash_v1_t *inp, size_t idx) -{ - return req_get_proof_by_hash_v1_get_leaf_hash((req_get_proof_by_hash_v1_t*)inp, idx); -} -int -req_get_proof_by_hash_v1_set_leaf_hash(req_get_proof_by_hash_v1_t *inp, size_t idx, uint8_t elt) -{ - trunnel_assert(idx < 32); - inp->leaf_hash[idx] = elt; - return 0; -} - -uint8_t * -req_get_proof_by_hash_v1_getarray_leaf_hash(req_get_proof_by_hash_v1_t *inp) -{ - return inp->leaf_hash; -} -const uint8_t * -req_get_proof_by_hash_v1_getconstarray_leaf_hash(const req_get_proof_by_hash_v1_t *inp) -{ - return (const uint8_t *)req_get_proof_by_hash_v1_getarray_leaf_hash((req_get_proof_by_hash_v1_t*)inp); -} -const char * -req_get_proof_by_hash_v1_check(const req_get_proof_by_hash_v1_t *obj) -{ - if (obj == NULL) - return "Object was NULL"; - if (obj->trunnel_error_code_) - return "A set function failed on this object"; - return NULL; -} - -ssize_t -req_get_proof_by_hash_v1_encoded_len(const req_get_proof_by_hash_v1_t *obj) -{ - ssize_t result = 0; - - if (NULL != req_get_proof_by_hash_v1_check(obj)) - return -1; - - - /* Length of u64 tree_size */ - result += 8; - - /* Length of u8 leaf_hash[32] */ - result += 32; - return result; -} -int -req_get_proof_by_hash_v1_clear_errors(req_get_proof_by_hash_v1_t *obj) -{ - int r = obj->trunnel_error_code_; - obj->trunnel_error_code_ = 0; - return r; -} -ssize_t -req_get_proof_by_hash_v1_encode(uint8_t *output, const size_t avail, const req_get_proof_by_hash_v1_t *obj) -{ - ssize_t result = 0; - size_t written = 0; - uint8_t *ptr = output; - const char *msg; -#ifdef TRUNNEL_CHECK_ENCODED_LEN - const ssize_t encoded_len = req_get_proof_by_hash_v1_encoded_len(obj); -#endif - - if (NULL != (msg = req_get_proof_by_hash_v1_check(obj))) - goto check_failed; - -#ifdef TRUNNEL_CHECK_ENCODED_LEN - trunnel_assert(encoded_len >= 0); -#endif - - /* Encode u64 tree_size */ - trunnel_assert(written <= avail); - if (avail - written < 8) - goto truncated; - trunnel_set_uint64(ptr, trunnel_htonll(obj->tree_size)); - written += 8; ptr += 8; - - /* Encode u8 leaf_hash[32] */ - trunnel_assert(written <= avail); - if (avail - written < 32) - goto truncated; - memcpy(ptr, obj->leaf_hash, 32); - written += 32; ptr += 32; - - - trunnel_assert(ptr == output + written); -#ifdef TRUNNEL_CHECK_ENCODED_LEN - { - trunnel_assert(encoded_len >= 0); - trunnel_assert((size_t)encoded_len == written); - } - -#endif - - return written; - - truncated: - result = -2; - goto fail; - check_failed: - (void)msg; - result = -1; - goto fail; - fail: - trunnel_assert(result < 0); - return result; -} - -/** As req_get_proof_by_hash_v1_parse(), but do not allocate the - * output object. - */ -static ssize_t -req_get_proof_by_hash_v1_parse_into(req_get_proof_by_hash_v1_t *obj, const uint8_t *input, const size_t len_in) -{ - const uint8_t *ptr = input; - size_t remaining = len_in; - ssize_t result = 0; - (void)result; - - /* Parse u64 tree_size */ - CHECK_REMAINING(8, truncated); - obj->tree_size = trunnel_ntohll(trunnel_get_uint64(ptr)); - remaining -= 8; ptr += 8; - - /* Parse u8 leaf_hash[32] */ - CHECK_REMAINING(32, truncated); - memcpy(obj->leaf_hash, ptr, 32); - remaining -= 32; ptr += 32; - trunnel_assert(ptr + remaining == input + len_in); - return len_in - remaining; - - truncated: - return -2; -} - -ssize_t -req_get_proof_by_hash_v1_parse(req_get_proof_by_hash_v1_t **output, const uint8_t *input, const size_t len_in) -{ - ssize_t result; - *output = req_get_proof_by_hash_v1_new(); - if (NULL == *output) - return -1; - result = req_get_proof_by_hash_v1_parse_into(*output, input, len_in); - if (result < 0) { - req_get_proof_by_hash_v1_free(*output); - *output = NULL; - } - return result; -} -sigident_ed25519_t * -sigident_ed25519_new(void) -{ - sigident_ed25519_t *val = trunnel_calloc(1, sizeof(sigident_ed25519_t)); - if (NULL == val) - return NULL; - return val; -} - -/** Release all storage held inside 'obj', but do not free 'obj'. - */ -static void -sigident_ed25519_clear(sigident_ed25519_t *obj) -{ - (void) obj; -} - -void -sigident_ed25519_free(sigident_ed25519_t *obj) -{ - if (obj == NULL) - return; - sigident_ed25519_clear(obj); - trunnel_memwipe(obj, sizeof(sigident_ed25519_t)); - trunnel_free_(obj); -} - -size_t -sigident_ed25519_getlen_signature(const sigident_ed25519_t *inp) -{ - (void)inp; return 64; -} - -uint8_t -sigident_ed25519_get_signature(sigident_ed25519_t *inp, size_t idx) -{ - trunnel_assert(idx < 64); - return inp->signature[idx]; -} - -uint8_t -sigident_ed25519_getconst_signature(const sigident_ed25519_t *inp, size_t idx) -{ - return sigident_ed25519_get_signature((sigident_ed25519_t*)inp, idx); -} -int -sigident_ed25519_set_signature(sigident_ed25519_t *inp, size_t idx, uint8_t elt) -{ - trunnel_assert(idx < 64); - inp->signature[idx] = elt; - return 0; -} - -uint8_t * -sigident_ed25519_getarray_signature(sigident_ed25519_t *inp) -{ - return inp->signature; -} -const uint8_t * -sigident_ed25519_getconstarray_signature(const sigident_ed25519_t *inp) -{ - return (const uint8_t *)sigident_ed25519_getarray_signature((sigident_ed25519_t*)inp); -} -size_t -sigident_ed25519_getlen_identifier(const sigident_ed25519_t *inp) -{ - (void)inp; return 32; -} - -uint8_t -sigident_ed25519_get_identifier(sigident_ed25519_t *inp, size_t idx) -{ - trunnel_assert(idx < 32); - return inp->identifier[idx]; -} - -uint8_t -sigident_ed25519_getconst_identifier(const sigident_ed25519_t *inp, size_t idx) -{ - return sigident_ed25519_get_identifier((sigident_ed25519_t*)inp, idx); -} -int -sigident_ed25519_set_identifier(sigident_ed25519_t *inp, size_t idx, uint8_t elt) -{ - trunnel_assert(idx < 32); - inp->identifier[idx] = elt; - return 0; -} - -uint8_t * -sigident_ed25519_getarray_identifier(sigident_ed25519_t *inp) -{ - return inp->identifier; -} -const uint8_t * -sigident_ed25519_getconstarray_identifier(const sigident_ed25519_t *inp) -{ - return (const uint8_t *)sigident_ed25519_getarray_identifier((sigident_ed25519_t*)inp); -} -const char * -sigident_ed25519_check(const sigident_ed25519_t *obj) -{ - if (obj == NULL) - return "Object was NULL"; - if (obj->trunnel_error_code_) - return "A set function failed on this object"; - return NULL; -} - -ssize_t -sigident_ed25519_encoded_len(const sigident_ed25519_t *obj) -{ - ssize_t result = 0; - - if (NULL != sigident_ed25519_check(obj)) - return -1; - - - /* Length of u8 signature[64] */ - result += 64; - - /* Length of u8 identifier[32] */ - result += 32; - return result; -} -int -sigident_ed25519_clear_errors(sigident_ed25519_t *obj) -{ - int r = obj->trunnel_error_code_; - obj->trunnel_error_code_ = 0; - return r; -} -ssize_t -sigident_ed25519_encode(uint8_t *output, const size_t avail, const sigident_ed25519_t *obj) -{ - ssize_t result = 0; - size_t written = 0; - uint8_t *ptr = output; - const char *msg; -#ifdef TRUNNEL_CHECK_ENCODED_LEN - const ssize_t encoded_len = sigident_ed25519_encoded_len(obj); -#endif - - if (NULL != (msg = sigident_ed25519_check(obj))) - goto check_failed; - -#ifdef TRUNNEL_CHECK_ENCODED_LEN - trunnel_assert(encoded_len >= 0); -#endif - - /* Encode u8 signature[64] */ - trunnel_assert(written <= avail); - if (avail - written < 64) - goto truncated; - memcpy(ptr, obj->signature, 64); - written += 64; ptr += 64; - - /* Encode u8 identifier[32] */ - trunnel_assert(written <= avail); - if (avail - written < 32) - goto truncated; - memcpy(ptr, obj->identifier, 32); - written += 32; ptr += 32; - - - trunnel_assert(ptr == output + written); -#ifdef TRUNNEL_CHECK_ENCODED_LEN - { - trunnel_assert(encoded_len >= 0); - trunnel_assert((size_t)encoded_len == written); - } - -#endif - - return written; - - truncated: - result = -2; - goto fail; - check_failed: - (void)msg; - result = -1; - goto fail; - fail: - trunnel_assert(result < 0); - return result; -} - -/** As sigident_ed25519_parse(), but do not allocate the output - * object. - */ -static ssize_t -sigident_ed25519_parse_into(sigident_ed25519_t *obj, const uint8_t *input, const size_t len_in) -{ - const uint8_t *ptr = input; - size_t remaining = len_in; - ssize_t result = 0; - (void)result; - - /* Parse u8 signature[64] */ - CHECK_REMAINING(64, truncated); - memcpy(obj->signature, ptr, 64); - remaining -= 64; ptr += 64; - - /* Parse u8 identifier[32] */ - CHECK_REMAINING(32, truncated); - memcpy(obj->identifier, ptr, 32); - remaining -= 32; ptr += 32; - trunnel_assert(ptr + remaining == input + len_in); - return len_in - remaining; - - truncated: - return -2; -} - -ssize_t -sigident_ed25519_parse(sigident_ed25519_t **output, const uint8_t *input, const size_t len_in) -{ - ssize_t result; - *output = sigident_ed25519_new(); - if (NULL == *output) - return -1; - result = sigident_ed25519_parse_into(*output, input, len_in); - if (result < 0) { - sigident_ed25519_free(*output); - *output = NULL; - } - return result; -} -signed_checksum32_ed25519_t * -signed_checksum32_ed25519_new(void) -{ - signed_checksum32_ed25519_t *val = trunnel_calloc(1, sizeof(signed_checksum32_ed25519_t)); - if (NULL == val) - return NULL; - val->length = 1; - return val; -} - -/** Release all storage held inside 'obj', but do not free 'obj'. - */ -static void -signed_checksum32_ed25519_clear(signed_checksum32_ed25519_t *obj) -{ - (void) obj; - TRUNNEL_DYNARRAY_WIPE(&obj->identifier); - TRUNNEL_DYNARRAY_CLEAR(&obj->identifier); -} - -void -signed_checksum32_ed25519_free(signed_checksum32_ed25519_t *obj) -{ - if (obj == NULL) - return; - signed_checksum32_ed25519_clear(obj); - trunnel_memwipe(obj, sizeof(signed_checksum32_ed25519_t)); - trunnel_free_(obj); -} - -size_t -signed_checksum32_ed25519_getlen_checksum(const signed_checksum32_ed25519_t *inp) -{ - (void)inp; return 32; -} - -uint8_t -signed_checksum32_ed25519_get_checksum(signed_checksum32_ed25519_t *inp, size_t idx) -{ - trunnel_assert(idx < 32); - return inp->checksum[idx]; -} - -uint8_t -signed_checksum32_ed25519_getconst_checksum(const signed_checksum32_ed25519_t *inp, size_t idx) -{ - return signed_checksum32_ed25519_get_checksum((signed_checksum32_ed25519_t*)inp, idx); -} -int -signed_checksum32_ed25519_set_checksum(signed_checksum32_ed25519_t *inp, size_t idx, uint8_t elt) -{ - trunnel_assert(idx < 32); - inp->checksum[idx] = elt; - return 0; -} - -uint8_t * -signed_checksum32_ed25519_getarray_checksum(signed_checksum32_ed25519_t *inp) -{ - return inp->checksum; -} -const uint8_t * -signed_checksum32_ed25519_getconstarray_checksum(const signed_checksum32_ed25519_t *inp) -{ - return (const uint8_t *)signed_checksum32_ed25519_getarray_checksum((signed_checksum32_ed25519_t*)inp); -} -uint64_t -signed_checksum32_ed25519_get_length(const signed_checksum32_ed25519_t *inp) -{ - return inp->length; -} -int -signed_checksum32_ed25519_set_length(signed_checksum32_ed25519_t *inp, uint64_t val) -{ - if (! (((val >= 1 && val <= 128)))) { - TRUNNEL_SET_ERROR_CODE(inp); - return -1; - } - inp->length = val; - return 0; -} -size_t -signed_checksum32_ed25519_getlen_identifier(const signed_checksum32_ed25519_t *inp) -{ - return TRUNNEL_DYNARRAY_LEN(&inp->identifier); -} - -uint8_t -signed_checksum32_ed25519_get_identifier(signed_checksum32_ed25519_t *inp, size_t idx) -{ - return TRUNNEL_DYNARRAY_GET(&inp->identifier, idx); -} - -uint8_t -signed_checksum32_ed25519_getconst_identifier(const signed_checksum32_ed25519_t *inp, size_t idx) -{ - return signed_checksum32_ed25519_get_identifier((signed_checksum32_ed25519_t*)inp, idx); -} -int -signed_checksum32_ed25519_set_identifier(signed_checksum32_ed25519_t *inp, size_t idx, uint8_t elt) -{ - TRUNNEL_DYNARRAY_SET(&inp->identifier, idx, elt); - return 0; -} -int -signed_checksum32_ed25519_add_identifier(signed_checksum32_ed25519_t *inp, uint8_t elt) -{ -#if SIZE_MAX >= UINT64_MAX - if (inp->identifier.n_ == UINT64_MAX) - goto trunnel_alloc_failed; -#endif - TRUNNEL_DYNARRAY_ADD(uint8_t, &inp->identifier, elt, {}); - return 0; - trunnel_alloc_failed: - TRUNNEL_SET_ERROR_CODE(inp); - return -1; -} - -uint8_t * -signed_checksum32_ed25519_getarray_identifier(signed_checksum32_ed25519_t *inp) -{ - return inp->identifier.elts_; -} -const uint8_t * -signed_checksum32_ed25519_getconstarray_identifier(const signed_checksum32_ed25519_t *inp) -{ - return (const uint8_t *)signed_checksum32_ed25519_getarray_identifier((signed_checksum32_ed25519_t*)inp); -} -int -signed_checksum32_ed25519_setlen_identifier(signed_checksum32_ed25519_t *inp, size_t newlen) -{ - uint8_t *newptr; -#if UINT64_MAX < SIZE_MAX - if (newlen > UINT64_MAX) - goto trunnel_alloc_failed; -#endif - newptr = trunnel_dynarray_setlen(&inp->identifier.allocated_, - &inp->identifier.n_, inp->identifier.elts_, newlen, - sizeof(inp->identifier.elts_[0]), (trunnel_free_fn_t) NULL, - &inp->trunnel_error_code_); - if (newlen != 0 && newptr == NULL) - goto trunnel_alloc_failed; - inp->identifier.elts_ = newptr; - return 0; - trunnel_alloc_failed: - TRUNNEL_SET_ERROR_CODE(inp); - return -1; -} -size_t -signed_checksum32_ed25519_getlen_signature(const signed_checksum32_ed25519_t *inp) -{ - (void)inp; return 64; -} - -uint8_t -signed_checksum32_ed25519_get_signature(signed_checksum32_ed25519_t *inp, size_t idx) -{ - trunnel_assert(idx < 64); - return inp->signature[idx]; -} - -uint8_t -signed_checksum32_ed25519_getconst_signature(const signed_checksum32_ed25519_t *inp, size_t idx) -{ - return signed_checksum32_ed25519_get_signature((signed_checksum32_ed25519_t*)inp, idx); -} -int -signed_checksum32_ed25519_set_signature(signed_checksum32_ed25519_t *inp, size_t idx, uint8_t elt) -{ - trunnel_assert(idx < 64); - inp->signature[idx] = elt; - return 0; -} - -uint8_t * -signed_checksum32_ed25519_getarray_signature(signed_checksum32_ed25519_t *inp) -{ - return inp->signature; -} -const uint8_t * -signed_checksum32_ed25519_getconstarray_signature(const signed_checksum32_ed25519_t *inp) -{ - return (const uint8_t *)signed_checksum32_ed25519_getarray_signature((signed_checksum32_ed25519_t*)inp); -} -size_t -signed_checksum32_ed25519_getlen_namespace(const signed_checksum32_ed25519_t *inp) -{ - (void)inp; return 32; -} - -uint8_t -signed_checksum32_ed25519_get_namespace(signed_checksum32_ed25519_t *inp, size_t idx) -{ - trunnel_assert(idx < 32); - return inp->namespace[idx]; -} - -uint8_t -signed_checksum32_ed25519_getconst_namespace(const signed_checksum32_ed25519_t *inp, size_t idx) -{ - return signed_checksum32_ed25519_get_namespace((signed_checksum32_ed25519_t*)inp, idx); -} -int -signed_checksum32_ed25519_set_namespace(signed_checksum32_ed25519_t *inp, size_t idx, uint8_t elt) -{ - trunnel_assert(idx < 32); - inp->namespace[idx] = elt; - return 0; -} - -uint8_t * -signed_checksum32_ed25519_getarray_namespace(signed_checksum32_ed25519_t *inp) -{ - return inp->namespace; -} -const uint8_t * -signed_checksum32_ed25519_getconstarray_namespace(const signed_checksum32_ed25519_t *inp) -{ - return (const uint8_t *)signed_checksum32_ed25519_getarray_namespace((signed_checksum32_ed25519_t*)inp); -} -const char * -signed_checksum32_ed25519_check(const signed_checksum32_ed25519_t *obj) -{ - if (obj == NULL) - return "Object was NULL"; - if (obj->trunnel_error_code_) - return "A set function failed on this object"; - if (! ((obj->length >= 1 && obj->length <= 128))) - return "Integer out of bounds"; - if (TRUNNEL_DYNARRAY_LEN(&obj->identifier) != obj->length) - return "Length mismatch for identifier"; - return NULL; -} - -ssize_t -signed_checksum32_ed25519_encoded_len(const signed_checksum32_ed25519_t *obj) -{ - ssize_t result = 0; - - if (NULL != signed_checksum32_ed25519_check(obj)) - return -1; - - - /* Length of u8 checksum[32] */ - result += 32; - - /* Length of u64 length IN [1..128] */ - result += 8; - - /* Length of u8 identifier[length] */ - result += TRUNNEL_DYNARRAY_LEN(&obj->identifier); - - /* Length of u8 signature[64] */ - result += 64; - - /* Length of u8 namespace[32] */ - result += 32; - return result; -} -int -signed_checksum32_ed25519_clear_errors(signed_checksum32_ed25519_t *obj) -{ - int r = obj->trunnel_error_code_; - obj->trunnel_error_code_ = 0; - return r; -} -ssize_t -signed_checksum32_ed25519_encode(uint8_t *output, const size_t avail, const signed_checksum32_ed25519_t *obj) -{ - ssize_t result = 0; - size_t written = 0; - uint8_t *ptr = output; - const char *msg; -#ifdef TRUNNEL_CHECK_ENCODED_LEN - const ssize_t encoded_len = signed_checksum32_ed25519_encoded_len(obj); -#endif - - if (NULL != (msg = signed_checksum32_ed25519_check(obj))) - goto check_failed; - -#ifdef TRUNNEL_CHECK_ENCODED_LEN - trunnel_assert(encoded_len >= 0); -#endif - - /* Encode u8 checksum[32] */ - trunnel_assert(written <= avail); - if (avail - written < 32) - goto truncated; - memcpy(ptr, obj->checksum, 32); - written += 32; ptr += 32; - - /* Encode u64 length IN [1..128] */ - trunnel_assert(written <= avail); - if (avail - written < 8) - goto truncated; - trunnel_set_uint64(ptr, trunnel_htonll(obj->length)); - written += 8; ptr += 8; - - /* Encode u8 identifier[length] */ - { - size_t elt_len = TRUNNEL_DYNARRAY_LEN(&obj->identifier); - trunnel_assert(obj->length == elt_len); - trunnel_assert(written <= avail); - if (avail - written < elt_len) - goto truncated; - if (elt_len) - memcpy(ptr, obj->identifier.elts_, elt_len); - written += elt_len; ptr += elt_len; - } - - /* Encode u8 signature[64] */ - trunnel_assert(written <= avail); - if (avail - written < 64) - goto truncated; - memcpy(ptr, obj->signature, 64); - written += 64; ptr += 64; - - /* Encode u8 namespace[32] */ - trunnel_assert(written <= avail); - if (avail - written < 32) - goto truncated; - memcpy(ptr, obj->namespace, 32); - written += 32; ptr += 32; - - - trunnel_assert(ptr == output + written); -#ifdef TRUNNEL_CHECK_ENCODED_LEN - { - trunnel_assert(encoded_len >= 0); - trunnel_assert((size_t)encoded_len == written); - } - -#endif - - return written; - - truncated: - result = -2; - goto fail; - check_failed: - (void)msg; - result = -1; - goto fail; - fail: - trunnel_assert(result < 0); - return result; -} - -/** As signed_checksum32_ed25519_parse(), but do not allocate the - * output object. - */ -static ssize_t -signed_checksum32_ed25519_parse_into(signed_checksum32_ed25519_t *obj, const uint8_t *input, const size_t len_in) -{ - const uint8_t *ptr = input; - size_t remaining = len_in; - ssize_t result = 0; - (void)result; - - /* Parse u8 checksum[32] */ - CHECK_REMAINING(32, truncated); - memcpy(obj->checksum, ptr, 32); - remaining -= 32; ptr += 32; - - /* Parse u64 length IN [1..128] */ - CHECK_REMAINING(8, truncated); - obj->length = trunnel_ntohll(trunnel_get_uint64(ptr)); - remaining -= 8; ptr += 8; - if (! ((obj->length >= 1 && obj->length <= 128))) - goto fail; - - /* Parse u8 identifier[length] */ - CHECK_REMAINING(obj->length, truncated); - TRUNNEL_DYNARRAY_EXPAND(uint8_t, &obj->identifier, obj->length, {}); - obj->identifier.n_ = obj->length; - if (obj->length) - memcpy(obj->identifier.elts_, ptr, obj->length); - ptr += obj->length; remaining -= obj->length; - - /* Parse u8 signature[64] */ - CHECK_REMAINING(64, truncated); - memcpy(obj->signature, ptr, 64); - remaining -= 64; ptr += 64; - - /* Parse u8 namespace[32] */ - CHECK_REMAINING(32, truncated); - memcpy(obj->namespace, ptr, 32); - remaining -= 32; ptr += 32; - trunnel_assert(ptr + remaining == input + len_in); - return len_in - remaining; - - truncated: - return -2; - trunnel_alloc_failed: - return -1; - fail: - result = -1; - return result; -} - -ssize_t -signed_checksum32_ed25519_parse(signed_checksum32_ed25519_t **output, const uint8_t *input, const size_t len_in) -{ - ssize_t result; - *output = signed_checksum32_ed25519_new(); - if (NULL == *output) - return -1; - result = signed_checksum32_ed25519_parse_into(*output, input, len_in); - if (result < 0) { - signed_checksum32_ed25519_free(*output); - *output = NULL; - } - return result; -} -consistency_proof_v1_t * -consistency_proof_v1_new(void) -{ - consistency_proof_v1_t *val = trunnel_calloc(1, sizeof(consistency_proof_v1_t)); - if (NULL == val) - return NULL; - val->magic = MAGIC_V1; - val->format = T_CONSISTENCY_PROOF_V1; - return val; -} - -/** Release all storage held inside 'obj', but do not free 'obj'. - */ -static void -consistency_proof_v1_clear(consistency_proof_v1_t *obj) -{ - (void) obj; - { - - unsigned idx; - for (idx = 0; idx < TRUNNEL_DYNARRAY_LEN(&obj->hashes); ++idx) { - hash_free(TRUNNEL_DYNARRAY_GET(&obj->hashes, idx)); - } - } - TRUNNEL_DYNARRAY_WIPE(&obj->hashes); - TRUNNEL_DYNARRAY_CLEAR(&obj->hashes); -} - -void -consistency_proof_v1_free(consistency_proof_v1_t *obj) -{ - if (obj == NULL) - return; - consistency_proof_v1_clear(obj); - trunnel_memwipe(obj, sizeof(consistency_proof_v1_t)); - trunnel_free_(obj); -} - -uint64_t -consistency_proof_v1_get_magic(const consistency_proof_v1_t *inp) -{ - return inp->magic; -} -int -consistency_proof_v1_set_magic(consistency_proof_v1_t *inp, uint64_t val) -{ - if (! ((val == MAGIC_V1))) { - TRUNNEL_SET_ERROR_CODE(inp); - return -1; - } - inp->magic = val; - return 0; -} -uint64_t -consistency_proof_v1_get_format(const consistency_proof_v1_t *inp) -{ - return inp->format; -} -int -consistency_proof_v1_set_format(consistency_proof_v1_t *inp, uint64_t val) -{ - if (! ((val == T_CONSISTENCY_PROOF_V1))) { - TRUNNEL_SET_ERROR_CODE(inp); - return -1; - } - inp->format = val; - return 0; -} -size_t -consistency_proof_v1_getlen_identifier(const consistency_proof_v1_t *inp) -{ - (void)inp; return 32; -} - -uint8_t -consistency_proof_v1_get_identifier(consistency_proof_v1_t *inp, size_t idx) -{ - trunnel_assert(idx < 32); - return inp->identifier[idx]; -} - -uint8_t -consistency_proof_v1_getconst_identifier(const consistency_proof_v1_t *inp, size_t idx) -{ - return consistency_proof_v1_get_identifier((consistency_proof_v1_t*)inp, idx); -} -int -consistency_proof_v1_set_identifier(consistency_proof_v1_t *inp, size_t idx, uint8_t elt) -{ - trunnel_assert(idx < 32); - inp->identifier[idx] = elt; - return 0; -} - -uint8_t * -consistency_proof_v1_getarray_identifier(consistency_proof_v1_t *inp) -{ - return inp->identifier; -} -const uint8_t * -consistency_proof_v1_getconstarray_identifier(const consistency_proof_v1_t *inp) -{ - return (const uint8_t *)consistency_proof_v1_getarray_identifier((consistency_proof_v1_t*)inp); -} -uint64_t -consistency_proof_v1_get_old_size(const consistency_proof_v1_t *inp) -{ - return inp->old_size; -} -int -consistency_proof_v1_set_old_size(consistency_proof_v1_t *inp, uint64_t val) -{ - inp->old_size = val; - return 0; -} -uint64_t -consistency_proof_v1_get_new_size(const consistency_proof_v1_t *inp) -{ - return inp->new_size; -} -int -consistency_proof_v1_set_new_size(consistency_proof_v1_t *inp, uint64_t val) -{ - inp->new_size = val; - return 0; -} -uint64_t -consistency_proof_v1_get_n_items(const consistency_proof_v1_t *inp) -{ - return inp->n_items; -} -int -consistency_proof_v1_set_n_items(consistency_proof_v1_t *inp, uint64_t val) -{ - inp->n_items = val; - return 0; -} -size_t -consistency_proof_v1_getlen_hashes(const consistency_proof_v1_t *inp) -{ - return TRUNNEL_DYNARRAY_LEN(&inp->hashes); -} - -struct hash_st * -consistency_proof_v1_get_hashes(consistency_proof_v1_t *inp, size_t idx) -{ - return TRUNNEL_DYNARRAY_GET(&inp->hashes, idx); -} - - const struct hash_st * -consistency_proof_v1_getconst_hashes(const consistency_proof_v1_t *inp, size_t idx) -{ - return consistency_proof_v1_get_hashes((consistency_proof_v1_t*)inp, idx); -} -int -consistency_proof_v1_set_hashes(consistency_proof_v1_t *inp, size_t idx, struct hash_st * elt) -{ - hash_t *oldval = TRUNNEL_DYNARRAY_GET(&inp->hashes, idx); - if (oldval && oldval != elt) - hash_free(oldval); - return consistency_proof_v1_set0_hashes(inp, idx, elt); -} -int -consistency_proof_v1_set0_hashes(consistency_proof_v1_t *inp, size_t idx, struct hash_st * elt) -{ - TRUNNEL_DYNARRAY_SET(&inp->hashes, idx, elt); - return 0; -} -int -consistency_proof_v1_add_hashes(consistency_proof_v1_t *inp, struct hash_st * elt) -{ -#if SIZE_MAX >= UINT64_MAX - if (inp->hashes.n_ == UINT64_MAX) - goto trunnel_alloc_failed; -#endif - TRUNNEL_DYNARRAY_ADD(struct hash_st *, &inp->hashes, elt, {}); - return 0; - trunnel_alloc_failed: - TRUNNEL_SET_ERROR_CODE(inp); - return -1; -} - -struct hash_st * * -consistency_proof_v1_getarray_hashes(consistency_proof_v1_t *inp) -{ - return inp->hashes.elts_; -} -const struct hash_st * const * -consistency_proof_v1_getconstarray_hashes(const consistency_proof_v1_t *inp) -{ - return (const struct hash_st * const *)consistency_proof_v1_getarray_hashes((consistency_proof_v1_t*)inp); -} -int -consistency_proof_v1_setlen_hashes(consistency_proof_v1_t *inp, size_t newlen) -{ - struct hash_st * *newptr; -#if UINT64_MAX < SIZE_MAX - if (newlen > UINT64_MAX) - goto trunnel_alloc_failed; -#endif - newptr = trunnel_dynarray_setlen(&inp->hashes.allocated_, - &inp->hashes.n_, inp->hashes.elts_, newlen, - sizeof(inp->hashes.elts_[0]), (trunnel_free_fn_t) hash_free, - &inp->trunnel_error_code_); - if (newlen != 0 && newptr == NULL) - goto trunnel_alloc_failed; - inp->hashes.elts_ = newptr; - return 0; - trunnel_alloc_failed: - TRUNNEL_SET_ERROR_CODE(inp); - return -1; -} -const char * -consistency_proof_v1_check(const consistency_proof_v1_t *obj) -{ - if (obj == NULL) - return "Object was NULL"; - if (obj->trunnel_error_code_) - return "A set function failed on this object"; - if (! (obj->magic == MAGIC_V1)) - return "Integer out of bounds"; - if (! (obj->format == T_CONSISTENCY_PROOF_V1)) - return "Integer out of bounds"; - { - const char *msg; - - unsigned idx; - for (idx = 0; idx < TRUNNEL_DYNARRAY_LEN(&obj->hashes); ++idx) { - if (NULL != (msg = hash_check(TRUNNEL_DYNARRAY_GET(&obj->hashes, idx)))) - return msg; - } - } - if (TRUNNEL_DYNARRAY_LEN(&obj->hashes) != obj->n_items) - return "Length mismatch for hashes"; - return NULL; -} - -ssize_t -consistency_proof_v1_encoded_len(const consistency_proof_v1_t *obj) -{ - ssize_t result = 0; - - if (NULL != consistency_proof_v1_check(obj)) - return -1; - - - /* Length of u64 magic IN [MAGIC_V1] */ - result += 8; - - /* Length of u64 format IN [T_CONSISTENCY_PROOF_V1] */ - result += 8; - - /* Length of u8 identifier[32] */ - result += 32; - - /* Length of u64 old_size */ - result += 8; - - /* Length of u64 new_size */ - result += 8; - - /* Length of u64 n_items */ - result += 8; - - /* Length of struct hash hashes[n_items] */ - { - - unsigned idx; - for (idx = 0; idx < TRUNNEL_DYNARRAY_LEN(&obj->hashes); ++idx) { - result += hash_encoded_len(TRUNNEL_DYNARRAY_GET(&obj->hashes, idx)); - } - } - return result; -} -int -consistency_proof_v1_clear_errors(consistency_proof_v1_t *obj) -{ - int r = obj->trunnel_error_code_; - obj->trunnel_error_code_ = 0; - return r; -} -ssize_t -consistency_proof_v1_encode(uint8_t *output, const size_t avail, const consistency_proof_v1_t *obj) -{ - ssize_t result = 0; - size_t written = 0; - uint8_t *ptr = output; - const char *msg; -#ifdef TRUNNEL_CHECK_ENCODED_LEN - const ssize_t encoded_len = consistency_proof_v1_encoded_len(obj); -#endif - - if (NULL != (msg = consistency_proof_v1_check(obj))) - goto check_failed; - -#ifdef TRUNNEL_CHECK_ENCODED_LEN - trunnel_assert(encoded_len >= 0); -#endif - - /* Encode u64 magic IN [MAGIC_V1] */ - trunnel_assert(written <= avail); - if (avail - written < 8) - goto truncated; - trunnel_set_uint64(ptr, trunnel_htonll(obj->magic)); - written += 8; ptr += 8; - - /* Encode u64 format IN [T_CONSISTENCY_PROOF_V1] */ - trunnel_assert(written <= avail); - if (avail - written < 8) - goto truncated; - trunnel_set_uint64(ptr, trunnel_htonll(obj->format)); - written += 8; ptr += 8; - - /* Encode u8 identifier[32] */ - trunnel_assert(written <= avail); - if (avail - written < 32) - goto truncated; - memcpy(ptr, obj->identifier, 32); - written += 32; ptr += 32; - - /* Encode u64 old_size */ - trunnel_assert(written <= avail); - if (avail - written < 8) - goto truncated; - trunnel_set_uint64(ptr, trunnel_htonll(obj->old_size)); - written += 8; ptr += 8; - - /* Encode u64 new_size */ - trunnel_assert(written <= avail); - if (avail - written < 8) - goto truncated; - trunnel_set_uint64(ptr, trunnel_htonll(obj->new_size)); - written += 8; ptr += 8; - - /* Encode u64 n_items */ - trunnel_assert(written <= avail); - if (avail - written < 8) - goto truncated; - trunnel_set_uint64(ptr, trunnel_htonll(obj->n_items)); - written += 8; ptr += 8; - - /* Encode struct hash hashes[n_items] */ - { - - unsigned idx; - for (idx = 0; idx < TRUNNEL_DYNARRAY_LEN(&obj->hashes); ++idx) { - trunnel_assert(written <= avail); - result = hash_encode(ptr, avail - written, TRUNNEL_DYNARRAY_GET(&obj->hashes, idx)); - if (result < 0) - goto fail; /* XXXXXXX !*/ - written += result; ptr += result; - } - } - - - trunnel_assert(ptr == output + written); -#ifdef TRUNNEL_CHECK_ENCODED_LEN - { - trunnel_assert(encoded_len >= 0); - trunnel_assert((size_t)encoded_len == written); - } - -#endif - - return written; - - truncated: - result = -2; - goto fail; - check_failed: - (void)msg; - result = -1; - goto fail; - fail: - trunnel_assert(result < 0); - return result; -} - -/** As consistency_proof_v1_parse(), but do not allocate the output - * object. - */ -static ssize_t -consistency_proof_v1_parse_into(consistency_proof_v1_t *obj, const uint8_t *input, const size_t len_in) -{ - const uint8_t *ptr = input; - size_t remaining = len_in; - ssize_t result = 0; - (void)result; - - /* Parse u64 magic IN [MAGIC_V1] */ - CHECK_REMAINING(8, truncated); - obj->magic = trunnel_ntohll(trunnel_get_uint64(ptr)); - remaining -= 8; ptr += 8; - if (! (obj->magic == MAGIC_V1)) - goto fail; - - /* Parse u64 format IN [T_CONSISTENCY_PROOF_V1] */ - CHECK_REMAINING(8, truncated); - obj->format = trunnel_ntohll(trunnel_get_uint64(ptr)); - remaining -= 8; ptr += 8; - if (! (obj->format == T_CONSISTENCY_PROOF_V1)) - goto fail; - - /* Parse u8 identifier[32] */ - CHECK_REMAINING(32, truncated); - memcpy(obj->identifier, ptr, 32); - remaining -= 32; ptr += 32; - - /* Parse u64 old_size */ - CHECK_REMAINING(8, truncated); - obj->old_size = trunnel_ntohll(trunnel_get_uint64(ptr)); - remaining -= 8; ptr += 8; - - /* Parse u64 new_size */ - CHECK_REMAINING(8, truncated); - obj->new_size = trunnel_ntohll(trunnel_get_uint64(ptr)); - remaining -= 8; ptr += 8; - - /* Parse u64 n_items */ - CHECK_REMAINING(8, truncated); - obj->n_items = trunnel_ntohll(trunnel_get_uint64(ptr)); - remaining -= 8; ptr += 8; - - /* Parse struct hash hashes[n_items] */ - TRUNNEL_DYNARRAY_EXPAND(hash_t *, &obj->hashes, obj->n_items, {}); - { - hash_t * elt; - unsigned idx; - for (idx = 0; idx < obj->n_items; ++idx) { - result = hash_parse(&elt, ptr, remaining); - if (result < 0) - goto relay_fail; - trunnel_assert((size_t)result <= remaining); - remaining -= result; ptr += result; - TRUNNEL_DYNARRAY_ADD(hash_t *, &obj->hashes, elt, {hash_free(elt);}); - } - } - trunnel_assert(ptr + remaining == input + len_in); - return len_in - remaining; - - truncated: - return -2; - relay_fail: - trunnel_assert(result < 0); - return result; - trunnel_alloc_failed: - return -1; - fail: - result = -1; - return result; -} - -ssize_t -consistency_proof_v1_parse(consistency_proof_v1_t **output, const uint8_t *input, const size_t len_in) -{ - ssize_t result; - *output = consistency_proof_v1_new(); - if (NULL == *output) - return -1; - result = consistency_proof_v1_parse_into(*output, input, len_in); - if (result < 0) { - consistency_proof_v1_free(*output); - *output = NULL; - } - return result; -} -entries_v1_t * -entries_v1_new(void) -{ - entries_v1_t *val = trunnel_calloc(1, sizeof(entries_v1_t)); - if (NULL == val) - return NULL; - val->magic = MAGIC_V1; - val->format = T_ENTRIES_V1; - return val; -} - -/** Release all storage held inside 'obj', but do not free 'obj'. - */ -static void -entries_v1_clear(entries_v1_t *obj) -{ - (void) obj; - { - - unsigned idx; - for (idx = 0; idx < TRUNNEL_DYNARRAY_LEN(&obj->checksums); ++idx) { - signed_checksum32_ed25519_free(TRUNNEL_DYNARRAY_GET(&obj->checksums, idx)); - } - } - TRUNNEL_DYNARRAY_WIPE(&obj->checksums); - TRUNNEL_DYNARRAY_CLEAR(&obj->checksums); -} - -void -entries_v1_free(entries_v1_t *obj) -{ - if (obj == NULL) - return; - entries_v1_clear(obj); - trunnel_memwipe(obj, sizeof(entries_v1_t)); - trunnel_free_(obj); -} - -uint64_t -entries_v1_get_magic(const entries_v1_t *inp) -{ - return inp->magic; -} -int -entries_v1_set_magic(entries_v1_t *inp, uint64_t val) -{ - if (! ((val == MAGIC_V1))) { - TRUNNEL_SET_ERROR_CODE(inp); - return -1; - } - inp->magic = val; - return 0; -} -uint64_t -entries_v1_get_format(const entries_v1_t *inp) -{ - return inp->format; -} -int -entries_v1_set_format(entries_v1_t *inp, uint64_t val) -{ - if (! ((val == T_ENTRIES_V1))) { - TRUNNEL_SET_ERROR_CODE(inp); - return -1; - } - inp->format = val; - return 0; -} -uint64_t -entries_v1_get_n_items(const entries_v1_t *inp) -{ - return inp->n_items; -} -int -entries_v1_set_n_items(entries_v1_t *inp, uint64_t val) -{ - inp->n_items = val; - return 0; -} -size_t -entries_v1_getlen_checksums(const entries_v1_t *inp) -{ - return TRUNNEL_DYNARRAY_LEN(&inp->checksums); -} - -struct signed_checksum32_ed25519_st * -entries_v1_get_checksums(entries_v1_t *inp, size_t idx) -{ - return TRUNNEL_DYNARRAY_GET(&inp->checksums, idx); -} - - const struct signed_checksum32_ed25519_st * -entries_v1_getconst_checksums(const entries_v1_t *inp, size_t idx) -{ - return entries_v1_get_checksums((entries_v1_t*)inp, idx); -} -int -entries_v1_set_checksums(entries_v1_t *inp, size_t idx, struct signed_checksum32_ed25519_st * elt) -{ - signed_checksum32_ed25519_t *oldval = TRUNNEL_DYNARRAY_GET(&inp->checksums, idx); - if (oldval && oldval != elt) - signed_checksum32_ed25519_free(oldval); - return entries_v1_set0_checksums(inp, idx, elt); -} -int -entries_v1_set0_checksums(entries_v1_t *inp, size_t idx, struct signed_checksum32_ed25519_st * elt) -{ - TRUNNEL_DYNARRAY_SET(&inp->checksums, idx, elt); - return 0; -} -int -entries_v1_add_checksums(entries_v1_t *inp, struct signed_checksum32_ed25519_st * elt) -{ -#if SIZE_MAX >= UINT64_MAX - if (inp->checksums.n_ == UINT64_MAX) - goto trunnel_alloc_failed; -#endif - TRUNNEL_DYNARRAY_ADD(struct signed_checksum32_ed25519_st *, &inp->checksums, elt, {}); - return 0; - trunnel_alloc_failed: - TRUNNEL_SET_ERROR_CODE(inp); - return -1; -} - -struct signed_checksum32_ed25519_st * * -entries_v1_getarray_checksums(entries_v1_t *inp) -{ - return inp->checksums.elts_; -} -const struct signed_checksum32_ed25519_st * const * -entries_v1_getconstarray_checksums(const entries_v1_t *inp) -{ - return (const struct signed_checksum32_ed25519_st * const *)entries_v1_getarray_checksums((entries_v1_t*)inp); -} -int -entries_v1_setlen_checksums(entries_v1_t *inp, size_t newlen) -{ - struct signed_checksum32_ed25519_st * *newptr; -#if UINT64_MAX < SIZE_MAX - if (newlen > UINT64_MAX) - goto trunnel_alloc_failed; -#endif - newptr = trunnel_dynarray_setlen(&inp->checksums.allocated_, - &inp->checksums.n_, inp->checksums.elts_, newlen, - sizeof(inp->checksums.elts_[0]), (trunnel_free_fn_t) signed_checksum32_ed25519_free, - &inp->trunnel_error_code_); - if (newlen != 0 && newptr == NULL) - goto trunnel_alloc_failed; - inp->checksums.elts_ = newptr; - return 0; - trunnel_alloc_failed: - TRUNNEL_SET_ERROR_CODE(inp); - return -1; -} -const char * -entries_v1_check(const entries_v1_t *obj) -{ - if (obj == NULL) - return "Object was NULL"; - if (obj->trunnel_error_code_) - return "A set function failed on this object"; - if (! (obj->magic == MAGIC_V1)) - return "Integer out of bounds"; - if (! (obj->format == T_ENTRIES_V1)) - return "Integer out of bounds"; - { - const char *msg; - - unsigned idx; - for (idx = 0; idx < TRUNNEL_DYNARRAY_LEN(&obj->checksums); ++idx) { - if (NULL != (msg = signed_checksum32_ed25519_check(TRUNNEL_DYNARRAY_GET(&obj->checksums, idx)))) - return msg; - } - } - if (TRUNNEL_DYNARRAY_LEN(&obj->checksums) != obj->n_items) - return "Length mismatch for checksums"; - return NULL; -} - -ssize_t -entries_v1_encoded_len(const entries_v1_t *obj) -{ - ssize_t result = 0; - - if (NULL != entries_v1_check(obj)) - return -1; - - - /* Length of u64 magic IN [MAGIC_V1] */ - result += 8; - - /* Length of u64 format IN [T_ENTRIES_V1] */ - result += 8; - - /* Length of u64 n_items */ - result += 8; - - /* Length of struct signed_checksum32_ed25519 checksums[n_items] */ - { - - unsigned idx; - for (idx = 0; idx < TRUNNEL_DYNARRAY_LEN(&obj->checksums); ++idx) { - result += signed_checksum32_ed25519_encoded_len(TRUNNEL_DYNARRAY_GET(&obj->checksums, idx)); - } - } - return result; -} -int -entries_v1_clear_errors(entries_v1_t *obj) -{ - int r = obj->trunnel_error_code_; - obj->trunnel_error_code_ = 0; - return r; -} -ssize_t -entries_v1_encode(uint8_t *output, const size_t avail, const entries_v1_t *obj) -{ - ssize_t result = 0; - size_t written = 0; - uint8_t *ptr = output; - const char *msg; -#ifdef TRUNNEL_CHECK_ENCODED_LEN - const ssize_t encoded_len = entries_v1_encoded_len(obj); -#endif - - if (NULL != (msg = entries_v1_check(obj))) - goto check_failed; - -#ifdef TRUNNEL_CHECK_ENCODED_LEN - trunnel_assert(encoded_len >= 0); -#endif - - /* Encode u64 magic IN [MAGIC_V1] */ - trunnel_assert(written <= avail); - if (avail - written < 8) - goto truncated; - trunnel_set_uint64(ptr, trunnel_htonll(obj->magic)); - written += 8; ptr += 8; - - /* Encode u64 format IN [T_ENTRIES_V1] */ - trunnel_assert(written <= avail); - if (avail - written < 8) - goto truncated; - trunnel_set_uint64(ptr, trunnel_htonll(obj->format)); - written += 8; ptr += 8; - - /* Encode u64 n_items */ - trunnel_assert(written <= avail); - if (avail - written < 8) - goto truncated; - trunnel_set_uint64(ptr, trunnel_htonll(obj->n_items)); - written += 8; ptr += 8; - - /* Encode struct signed_checksum32_ed25519 checksums[n_items] */ - { - - unsigned idx; - for (idx = 0; idx < TRUNNEL_DYNARRAY_LEN(&obj->checksums); ++idx) { - trunnel_assert(written <= avail); - result = signed_checksum32_ed25519_encode(ptr, avail - written, TRUNNEL_DYNARRAY_GET(&obj->checksums, idx)); - if (result < 0) - goto fail; /* XXXXXXX !*/ - written += result; ptr += result; - } - } - - - trunnel_assert(ptr == output + written); -#ifdef TRUNNEL_CHECK_ENCODED_LEN - { - trunnel_assert(encoded_len >= 0); - trunnel_assert((size_t)encoded_len == written); - } - -#endif - - return written; - - truncated: - result = -2; - goto fail; - check_failed: - (void)msg; - result = -1; - goto fail; - fail: - trunnel_assert(result < 0); - return result; -} - -/** As entries_v1_parse(), but do not allocate the output object. - */ -static ssize_t -entries_v1_parse_into(entries_v1_t *obj, const uint8_t *input, const size_t len_in) -{ - const uint8_t *ptr = input; - size_t remaining = len_in; - ssize_t result = 0; - (void)result; - - /* Parse u64 magic IN [MAGIC_V1] */ - CHECK_REMAINING(8, truncated); - obj->magic = trunnel_ntohll(trunnel_get_uint64(ptr)); - remaining -= 8; ptr += 8; - if (! (obj->magic == MAGIC_V1)) - goto fail; - - /* Parse u64 format IN [T_ENTRIES_V1] */ - CHECK_REMAINING(8, truncated); - obj->format = trunnel_ntohll(trunnel_get_uint64(ptr)); - remaining -= 8; ptr += 8; - if (! (obj->format == T_ENTRIES_V1)) - goto fail; - - /* Parse u64 n_items */ - CHECK_REMAINING(8, truncated); - obj->n_items = trunnel_ntohll(trunnel_get_uint64(ptr)); - remaining -= 8; ptr += 8; - - /* Parse struct signed_checksum32_ed25519 checksums[n_items] */ - TRUNNEL_DYNARRAY_EXPAND(signed_checksum32_ed25519_t *, &obj->checksums, obj->n_items, {}); - { - signed_checksum32_ed25519_t * elt; - unsigned idx; - for (idx = 0; idx < obj->n_items; ++idx) { - result = signed_checksum32_ed25519_parse(&elt, ptr, remaining); - if (result < 0) - goto relay_fail; - trunnel_assert((size_t)result <= remaining); - remaining -= result; ptr += result; - TRUNNEL_DYNARRAY_ADD(signed_checksum32_ed25519_t *, &obj->checksums, elt, {signed_checksum32_ed25519_free(elt);}); - } - } - trunnel_assert(ptr + remaining == input + len_in); - return len_in - remaining; - - truncated: - return -2; - relay_fail: - trunnel_assert(result < 0); - return result; - trunnel_alloc_failed: - return -1; - fail: - result = -1; - return result; -} - -ssize_t -entries_v1_parse(entries_v1_t **output, const uint8_t *input, const size_t len_in) -{ - ssize_t result; - *output = entries_v1_new(); - if (NULL == *output) - return -1; - result = entries_v1_parse_into(*output, input, len_in); - if (result < 0) { - entries_v1_free(*output); - *output = NULL; - } - return result; -} -inclusion_proof_v1_t * -inclusion_proof_v1_new(void) -{ - inclusion_proof_v1_t *val = trunnel_calloc(1, sizeof(inclusion_proof_v1_t)); - if (NULL == val) - return NULL; - val->magic = MAGIC_V1; - val->format = T_INCLUSION_PROOF_V1; - return val; -} - -/** Release all storage held inside 'obj', but do not free 'obj'. - */ -static void -inclusion_proof_v1_clear(inclusion_proof_v1_t *obj) -{ - (void) obj; - { - - unsigned idx; - for (idx = 0; idx < TRUNNEL_DYNARRAY_LEN(&obj->hashes); ++idx) { - hash_free(TRUNNEL_DYNARRAY_GET(&obj->hashes, idx)); - } - } - TRUNNEL_DYNARRAY_WIPE(&obj->hashes); - TRUNNEL_DYNARRAY_CLEAR(&obj->hashes); -} - -void -inclusion_proof_v1_free(inclusion_proof_v1_t *obj) -{ - if (obj == NULL) - return; - inclusion_proof_v1_clear(obj); - trunnel_memwipe(obj, sizeof(inclusion_proof_v1_t)); - trunnel_free_(obj); -} - -uint64_t -inclusion_proof_v1_get_magic(const inclusion_proof_v1_t *inp) -{ - return inp->magic; -} -int -inclusion_proof_v1_set_magic(inclusion_proof_v1_t *inp, uint64_t val) -{ - if (! ((val == MAGIC_V1))) { - TRUNNEL_SET_ERROR_CODE(inp); - return -1; - } - inp->magic = val; - return 0; -} -uint64_t -inclusion_proof_v1_get_format(const inclusion_proof_v1_t *inp) -{ - return inp->format; -} -int -inclusion_proof_v1_set_format(inclusion_proof_v1_t *inp, uint64_t val) -{ - if (! ((val == T_INCLUSION_PROOF_V1))) { - TRUNNEL_SET_ERROR_CODE(inp); - return -1; - } - inp->format = val; - return 0; -} -size_t -inclusion_proof_v1_getlen_identifier(const inclusion_proof_v1_t *inp) -{ - (void)inp; return 32; -} - -uint8_t -inclusion_proof_v1_get_identifier(inclusion_proof_v1_t *inp, size_t idx) -{ - trunnel_assert(idx < 32); - return inp->identifier[idx]; -} - -uint8_t -inclusion_proof_v1_getconst_identifier(const inclusion_proof_v1_t *inp, size_t idx) -{ - return inclusion_proof_v1_get_identifier((inclusion_proof_v1_t*)inp, idx); -} -int -inclusion_proof_v1_set_identifier(inclusion_proof_v1_t *inp, size_t idx, uint8_t elt) -{ - trunnel_assert(idx < 32); - inp->identifier[idx] = elt; - return 0; -} - -uint8_t * -inclusion_proof_v1_getarray_identifier(inclusion_proof_v1_t *inp) -{ - return inp->identifier; -} -const uint8_t * -inclusion_proof_v1_getconstarray_identifier(const inclusion_proof_v1_t *inp) -{ - return (const uint8_t *)inclusion_proof_v1_getarray_identifier((inclusion_proof_v1_t*)inp); -} -uint64_t -inclusion_proof_v1_get_tree_size(const inclusion_proof_v1_t *inp) -{ - return inp->tree_size; -} -int -inclusion_proof_v1_set_tree_size(inclusion_proof_v1_t *inp, uint64_t val) -{ - inp->tree_size = val; - return 0; -} -uint64_t -inclusion_proof_v1_get_leaf_index(const inclusion_proof_v1_t *inp) -{ - return inp->leaf_index; -} -int -inclusion_proof_v1_set_leaf_index(inclusion_proof_v1_t *inp, uint64_t val) -{ - inp->leaf_index = val; - return 0; -} -uint64_t -inclusion_proof_v1_get_n_items(const inclusion_proof_v1_t *inp) -{ - return inp->n_items; -} -int -inclusion_proof_v1_set_n_items(inclusion_proof_v1_t *inp, uint64_t val) -{ - inp->n_items = val; - return 0; -} -size_t -inclusion_proof_v1_getlen_hashes(const inclusion_proof_v1_t *inp) -{ - return TRUNNEL_DYNARRAY_LEN(&inp->hashes); -} - -struct hash_st * -inclusion_proof_v1_get_hashes(inclusion_proof_v1_t *inp, size_t idx) -{ - return TRUNNEL_DYNARRAY_GET(&inp->hashes, idx); -} - - const struct hash_st * -inclusion_proof_v1_getconst_hashes(const inclusion_proof_v1_t *inp, size_t idx) -{ - return inclusion_proof_v1_get_hashes((inclusion_proof_v1_t*)inp, idx); -} -int -inclusion_proof_v1_set_hashes(inclusion_proof_v1_t *inp, size_t idx, struct hash_st * elt) -{ - hash_t *oldval = TRUNNEL_DYNARRAY_GET(&inp->hashes, idx); - if (oldval && oldval != elt) - hash_free(oldval); - return inclusion_proof_v1_set0_hashes(inp, idx, elt); -} -int -inclusion_proof_v1_set0_hashes(inclusion_proof_v1_t *inp, size_t idx, struct hash_st * elt) -{ - TRUNNEL_DYNARRAY_SET(&inp->hashes, idx, elt); - return 0; -} -int -inclusion_proof_v1_add_hashes(inclusion_proof_v1_t *inp, struct hash_st * elt) -{ -#if SIZE_MAX >= UINT64_MAX - if (inp->hashes.n_ == UINT64_MAX) - goto trunnel_alloc_failed; -#endif - TRUNNEL_DYNARRAY_ADD(struct hash_st *, &inp->hashes, elt, {}); - return 0; - trunnel_alloc_failed: - TRUNNEL_SET_ERROR_CODE(inp); - return -1; -} - -struct hash_st * * -inclusion_proof_v1_getarray_hashes(inclusion_proof_v1_t *inp) -{ - return inp->hashes.elts_; -} -const struct hash_st * const * -inclusion_proof_v1_getconstarray_hashes(const inclusion_proof_v1_t *inp) -{ - return (const struct hash_st * const *)inclusion_proof_v1_getarray_hashes((inclusion_proof_v1_t*)inp); -} -int -inclusion_proof_v1_setlen_hashes(inclusion_proof_v1_t *inp, size_t newlen) -{ - struct hash_st * *newptr; -#if UINT64_MAX < SIZE_MAX - if (newlen > UINT64_MAX) - goto trunnel_alloc_failed; -#endif - newptr = trunnel_dynarray_setlen(&inp->hashes.allocated_, - &inp->hashes.n_, inp->hashes.elts_, newlen, - sizeof(inp->hashes.elts_[0]), (trunnel_free_fn_t) hash_free, - &inp->trunnel_error_code_); - if (newlen != 0 && newptr == NULL) - goto trunnel_alloc_failed; - inp->hashes.elts_ = newptr; - return 0; - trunnel_alloc_failed: - TRUNNEL_SET_ERROR_CODE(inp); - return -1; -} -const char * -inclusion_proof_v1_check(const inclusion_proof_v1_t *obj) -{ - if (obj == NULL) - return "Object was NULL"; - if (obj->trunnel_error_code_) - return "A set function failed on this object"; - if (! (obj->magic == MAGIC_V1)) - return "Integer out of bounds"; - if (! (obj->format == T_INCLUSION_PROOF_V1)) - return "Integer out of bounds"; - { - const char *msg; - - unsigned idx; - for (idx = 0; idx < TRUNNEL_DYNARRAY_LEN(&obj->hashes); ++idx) { - if (NULL != (msg = hash_check(TRUNNEL_DYNARRAY_GET(&obj->hashes, idx)))) - return msg; - } - } - if (TRUNNEL_DYNARRAY_LEN(&obj->hashes) != obj->n_items) - return "Length mismatch for hashes"; - return NULL; -} - -ssize_t -inclusion_proof_v1_encoded_len(const inclusion_proof_v1_t *obj) -{ - ssize_t result = 0; - - if (NULL != inclusion_proof_v1_check(obj)) - return -1; - - - /* Length of u64 magic IN [MAGIC_V1] */ - result += 8; - - /* Length of u64 format IN [T_INCLUSION_PROOF_V1] */ - result += 8; - - /* Length of u8 identifier[32] */ - result += 32; - - /* Length of u64 tree_size */ - result += 8; - - /* Length of u64 leaf_index */ - result += 8; - - /* Length of u64 n_items */ - result += 8; - - /* Length of struct hash hashes[n_items] */ - { - - unsigned idx; - for (idx = 0; idx < TRUNNEL_DYNARRAY_LEN(&obj->hashes); ++idx) { - result += hash_encoded_len(TRUNNEL_DYNARRAY_GET(&obj->hashes, idx)); - } - } - return result; -} -int -inclusion_proof_v1_clear_errors(inclusion_proof_v1_t *obj) -{ - int r = obj->trunnel_error_code_; - obj->trunnel_error_code_ = 0; - return r; -} -ssize_t -inclusion_proof_v1_encode(uint8_t *output, const size_t avail, const inclusion_proof_v1_t *obj) -{ - ssize_t result = 0; - size_t written = 0; - uint8_t *ptr = output; - const char *msg; -#ifdef TRUNNEL_CHECK_ENCODED_LEN - const ssize_t encoded_len = inclusion_proof_v1_encoded_len(obj); -#endif - - if (NULL != (msg = inclusion_proof_v1_check(obj))) - goto check_failed; - -#ifdef TRUNNEL_CHECK_ENCODED_LEN - trunnel_assert(encoded_len >= 0); -#endif - - /* Encode u64 magic IN [MAGIC_V1] */ - trunnel_assert(written <= avail); - if (avail - written < 8) - goto truncated; - trunnel_set_uint64(ptr, trunnel_htonll(obj->magic)); - written += 8; ptr += 8; - - /* Encode u64 format IN [T_INCLUSION_PROOF_V1] */ - trunnel_assert(written <= avail); - if (avail - written < 8) - goto truncated; - trunnel_set_uint64(ptr, trunnel_htonll(obj->format)); - written += 8; ptr += 8; - - /* Encode u8 identifier[32] */ - trunnel_assert(written <= avail); - if (avail - written < 32) - goto truncated; - memcpy(ptr, obj->identifier, 32); - written += 32; ptr += 32; - - /* Encode u64 tree_size */ - trunnel_assert(written <= avail); - if (avail - written < 8) - goto truncated; - trunnel_set_uint64(ptr, trunnel_htonll(obj->tree_size)); - written += 8; ptr += 8; - - /* Encode u64 leaf_index */ - trunnel_assert(written <= avail); - if (avail - written < 8) - goto truncated; - trunnel_set_uint64(ptr, trunnel_htonll(obj->leaf_index)); - written += 8; ptr += 8; - - /* Encode u64 n_items */ - trunnel_assert(written <= avail); - if (avail - written < 8) - goto truncated; - trunnel_set_uint64(ptr, trunnel_htonll(obj->n_items)); - written += 8; ptr += 8; - - /* Encode struct hash hashes[n_items] */ - { - - unsigned idx; - for (idx = 0; idx < TRUNNEL_DYNARRAY_LEN(&obj->hashes); ++idx) { - trunnel_assert(written <= avail); - result = hash_encode(ptr, avail - written, TRUNNEL_DYNARRAY_GET(&obj->hashes, idx)); - if (result < 0) - goto fail; /* XXXXXXX !*/ - written += result; ptr += result; - } - } - - - trunnel_assert(ptr == output + written); -#ifdef TRUNNEL_CHECK_ENCODED_LEN - { - trunnel_assert(encoded_len >= 0); - trunnel_assert((size_t)encoded_len == written); - } - -#endif - - return written; - - truncated: - result = -2; - goto fail; - check_failed: - (void)msg; - result = -1; - goto fail; - fail: - trunnel_assert(result < 0); - return result; -} - -/** As inclusion_proof_v1_parse(), but do not allocate the output - * object. - */ -static ssize_t -inclusion_proof_v1_parse_into(inclusion_proof_v1_t *obj, const uint8_t *input, const size_t len_in) -{ - const uint8_t *ptr = input; - size_t remaining = len_in; - ssize_t result = 0; - (void)result; - - /* Parse u64 magic IN [MAGIC_V1] */ - CHECK_REMAINING(8, truncated); - obj->magic = trunnel_ntohll(trunnel_get_uint64(ptr)); - remaining -= 8; ptr += 8; - if (! (obj->magic == MAGIC_V1)) - goto fail; - - /* Parse u64 format IN [T_INCLUSION_PROOF_V1] */ - CHECK_REMAINING(8, truncated); - obj->format = trunnel_ntohll(trunnel_get_uint64(ptr)); - remaining -= 8; ptr += 8; - if (! (obj->format == T_INCLUSION_PROOF_V1)) - goto fail; - - /* Parse u8 identifier[32] */ - CHECK_REMAINING(32, truncated); - memcpy(obj->identifier, ptr, 32); - remaining -= 32; ptr += 32; - - /* Parse u64 tree_size */ - CHECK_REMAINING(8, truncated); - obj->tree_size = trunnel_ntohll(trunnel_get_uint64(ptr)); - remaining -= 8; ptr += 8; - - /* Parse u64 leaf_index */ - CHECK_REMAINING(8, truncated); - obj->leaf_index = trunnel_ntohll(trunnel_get_uint64(ptr)); - remaining -= 8; ptr += 8; - - /* Parse u64 n_items */ - CHECK_REMAINING(8, truncated); - obj->n_items = trunnel_ntohll(trunnel_get_uint64(ptr)); - remaining -= 8; ptr += 8; - - /* Parse struct hash hashes[n_items] */ - TRUNNEL_DYNARRAY_EXPAND(hash_t *, &obj->hashes, obj->n_items, {}); - { - hash_t * elt; - unsigned idx; - for (idx = 0; idx < obj->n_items; ++idx) { - result = hash_parse(&elt, ptr, remaining); - if (result < 0) - goto relay_fail; - trunnel_assert((size_t)result <= remaining); - remaining -= result; ptr += result; - TRUNNEL_DYNARRAY_ADD(hash_t *, &obj->hashes, elt, {hash_free(elt);}); - } - } - trunnel_assert(ptr + remaining == input + len_in); - return len_in - remaining; - - truncated: - return -2; - relay_fail: - trunnel_assert(result < 0); - return result; - trunnel_alloc_failed: - return -1; - fail: - result = -1; - return result; -} - -ssize_t -inclusion_proof_v1_parse(inclusion_proof_v1_t **output, const uint8_t *input, const size_t len_in) -{ - ssize_t result; - *output = inclusion_proof_v1_new(); - if (NULL == *output) - return -1; - result = inclusion_proof_v1_parse_into(*output, input, len_in); - if (result < 0) { - inclusion_proof_v1_free(*output); - *output = NULL; - } - return result; -} -request_v1_t * -request_v1_new(void) -{ - request_v1_t *val = trunnel_calloc(1, sizeof(request_v1_t)); - if (NULL == val) - return NULL; - val->magic = MAGIC_V1; - val->format = T_GET_CONSISTENCY_PROOF_V1; - return val; -} - -/** Release all storage held inside 'obj', but do not free 'obj'. - */ -static void -request_v1_clear(request_v1_t *obj) -{ - (void) obj; - req_get_entries_v1_free(obj->request_get_entries); - obj->request_get_entries = NULL; - req_get_proof_by_hash_v1_free(obj->request_get_proof_by_hash); - obj->request_get_proof_by_hash = NULL; - req_get_consistency_proof_v1_free(obj->request_get_consistency_proof); - obj->request_get_consistency_proof = NULL; -} - -void -request_v1_free(request_v1_t *obj) -{ - if (obj == NULL) - return; - request_v1_clear(obj); - trunnel_memwipe(obj, sizeof(request_v1_t)); - trunnel_free_(obj); -} - -uint64_t -request_v1_get_magic(const request_v1_t *inp) -{ - return inp->magic; -} -int -request_v1_set_magic(request_v1_t *inp, uint64_t val) -{ - if (! ((val == MAGIC_V1))) { - TRUNNEL_SET_ERROR_CODE(inp); - return -1; - } - inp->magic = val; - return 0; -} -uint64_t -request_v1_get_format(const request_v1_t *inp) -{ - return inp->format; -} -int -request_v1_set_format(request_v1_t *inp, uint64_t val) -{ - if (! ((val == T_GET_CONSISTENCY_PROOF_V1 || val == T_GET_ENTRIES_V1 || val == T_GET_PROOF_BY_HASH_V1))) { - TRUNNEL_SET_ERROR_CODE(inp); - return -1; - } - inp->format = val; - return 0; -} -struct req_get_entries_v1_st * -request_v1_get_request_get_entries(request_v1_t *inp) -{ - return inp->request_get_entries; -} -const struct req_get_entries_v1_st * -request_v1_getconst_request_get_entries(const request_v1_t *inp) -{ - return request_v1_get_request_get_entries((request_v1_t*) inp); -} -int -request_v1_set_request_get_entries(request_v1_t *inp, struct req_get_entries_v1_st *val) -{ - if (inp->request_get_entries && inp->request_get_entries != val) - req_get_entries_v1_free(inp->request_get_entries); - return request_v1_set0_request_get_entries(inp, val); -} -int -request_v1_set0_request_get_entries(request_v1_t *inp, struct req_get_entries_v1_st *val) -{ - inp->request_get_entries = val; - return 0; -} -struct req_get_proof_by_hash_v1_st * -request_v1_get_request_get_proof_by_hash(request_v1_t *inp) -{ - return inp->request_get_proof_by_hash; -} -const struct req_get_proof_by_hash_v1_st * -request_v1_getconst_request_get_proof_by_hash(const request_v1_t *inp) -{ - return request_v1_get_request_get_proof_by_hash((request_v1_t*) inp); -} -int -request_v1_set_request_get_proof_by_hash(request_v1_t *inp, struct req_get_proof_by_hash_v1_st *val) -{ - if (inp->request_get_proof_by_hash && inp->request_get_proof_by_hash != val) - req_get_proof_by_hash_v1_free(inp->request_get_proof_by_hash); - return request_v1_set0_request_get_proof_by_hash(inp, val); -} -int -request_v1_set0_request_get_proof_by_hash(request_v1_t *inp, struct req_get_proof_by_hash_v1_st *val) -{ - inp->request_get_proof_by_hash = val; - return 0; -} -struct req_get_consistency_proof_v1_st * -request_v1_get_request_get_consistency_proof(request_v1_t *inp) -{ - return inp->request_get_consistency_proof; -} -const struct req_get_consistency_proof_v1_st * -request_v1_getconst_request_get_consistency_proof(const request_v1_t *inp) -{ - return request_v1_get_request_get_consistency_proof((request_v1_t*) inp); -} -int -request_v1_set_request_get_consistency_proof(request_v1_t *inp, struct req_get_consistency_proof_v1_st *val) -{ - if (inp->request_get_consistency_proof && inp->request_get_consistency_proof != val) - req_get_consistency_proof_v1_free(inp->request_get_consistency_proof); - return request_v1_set0_request_get_consistency_proof(inp, val); -} -int -request_v1_set0_request_get_consistency_proof(request_v1_t *inp, struct req_get_consistency_proof_v1_st *val) -{ - inp->request_get_consistency_proof = val; - return 0; -} -const char * -request_v1_check(const request_v1_t *obj) -{ - if (obj == NULL) - return "Object was NULL"; - if (obj->trunnel_error_code_) - return "A set function failed on this object"; - if (! (obj->magic == MAGIC_V1)) - return "Integer out of bounds"; - if (! (obj->format == T_GET_CONSISTENCY_PROOF_V1 || obj->format == T_GET_ENTRIES_V1 || obj->format == T_GET_PROOF_BY_HASH_V1)) - return "Integer out of bounds"; - switch (obj->format) { - - case T_GET_ENTRIES_V1: - { - const char *msg; - if (NULL != (msg = req_get_entries_v1_check(obj->request_get_entries))) - return msg; - } - break; - - case T_GET_PROOF_BY_HASH_V1: - { - const char *msg; - if (NULL != (msg = req_get_proof_by_hash_v1_check(obj->request_get_proof_by_hash))) - return msg; - } - break; - - case T_GET_CONSISTENCY_PROOF_V1: - { - const char *msg; - if (NULL != (msg = req_get_consistency_proof_v1_check(obj->request_get_consistency_proof))) - return msg; - } - break; - - default: - return "Bad tag for union"; - break; - } - return NULL; -} - -ssize_t -request_v1_encoded_len(const request_v1_t *obj) -{ - ssize_t result = 0; - - if (NULL != request_v1_check(obj)) - return -1; - - - /* Length of u64 magic IN [MAGIC_V1] */ - result += 8; - - /* Length of u64 format IN [T_GET_CONSISTENCY_PROOF_V1, T_GET_ENTRIES_V1, T_GET_PROOF_BY_HASH_V1] */ - result += 8; - switch (obj->format) { - - case T_GET_ENTRIES_V1: - - /* Length of struct req_get_entries_v1 request_get_entries */ - result += req_get_entries_v1_encoded_len(obj->request_get_entries); - break; - - case T_GET_PROOF_BY_HASH_V1: - - /* Length of struct req_get_proof_by_hash_v1 request_get_proof_by_hash */ - result += req_get_proof_by_hash_v1_encoded_len(obj->request_get_proof_by_hash); - break; - - case T_GET_CONSISTENCY_PROOF_V1: - - /* Length of struct req_get_consistency_proof_v1 request_get_consistency_proof */ - result += req_get_consistency_proof_v1_encoded_len(obj->request_get_consistency_proof); - break; - - default: - trunnel_assert(0); - break; - } - return result; -} -int -request_v1_clear_errors(request_v1_t *obj) -{ - int r = obj->trunnel_error_code_; - obj->trunnel_error_code_ = 0; - return r; -} -ssize_t -request_v1_encode(uint8_t *output, const size_t avail, const request_v1_t *obj) -{ - ssize_t result = 0; - size_t written = 0; - uint8_t *ptr = output; - const char *msg; -#ifdef TRUNNEL_CHECK_ENCODED_LEN - const ssize_t encoded_len = request_v1_encoded_len(obj); -#endif - - if (NULL != (msg = request_v1_check(obj))) - goto check_failed; - -#ifdef TRUNNEL_CHECK_ENCODED_LEN - trunnel_assert(encoded_len >= 0); -#endif - - /* Encode u64 magic IN [MAGIC_V1] */ - trunnel_assert(written <= avail); - if (avail - written < 8) - goto truncated; - trunnel_set_uint64(ptr, trunnel_htonll(obj->magic)); - written += 8; ptr += 8; - - /* Encode u64 format IN [T_GET_CONSISTENCY_PROOF_V1, T_GET_ENTRIES_V1, T_GET_PROOF_BY_HASH_V1] */ - trunnel_assert(written <= avail); - if (avail - written < 8) - goto truncated; - trunnel_set_uint64(ptr, trunnel_htonll(obj->format)); - written += 8; ptr += 8; - - /* Encode union request[format] */ - trunnel_assert(written <= avail); - switch (obj->format) { - - case T_GET_ENTRIES_V1: - - /* Encode struct req_get_entries_v1 request_get_entries */ - trunnel_assert(written <= avail); - result = req_get_entries_v1_encode(ptr, avail - written, obj->request_get_entries); - if (result < 0) - goto fail; /* XXXXXXX !*/ - written += result; ptr += result; - break; - - case T_GET_PROOF_BY_HASH_V1: - - /* Encode struct req_get_proof_by_hash_v1 request_get_proof_by_hash */ - trunnel_assert(written <= avail); - result = req_get_proof_by_hash_v1_encode(ptr, avail - written, obj->request_get_proof_by_hash); - if (result < 0) - goto fail; /* XXXXXXX !*/ - written += result; ptr += result; - break; - - case T_GET_CONSISTENCY_PROOF_V1: - - /* Encode struct req_get_consistency_proof_v1 request_get_consistency_proof */ - trunnel_assert(written <= avail); - result = req_get_consistency_proof_v1_encode(ptr, avail - written, obj->request_get_consistency_proof); - if (result < 0) - goto fail; /* XXXXXXX !*/ - written += result; ptr += result; - break; - - default: - trunnel_assert(0); - break; - } - - - trunnel_assert(ptr == output + written); -#ifdef TRUNNEL_CHECK_ENCODED_LEN - { - trunnel_assert(encoded_len >= 0); - trunnel_assert((size_t)encoded_len == written); - } - -#endif - - return written; - - truncated: - result = -2; - goto fail; - check_failed: - (void)msg; - result = -1; - goto fail; - fail: - trunnel_assert(result < 0); - return result; -} - -/** As request_v1_parse(), but do not allocate the output object. - */ -static ssize_t -request_v1_parse_into(request_v1_t *obj, const uint8_t *input, const size_t len_in) -{ - const uint8_t *ptr = input; - size_t remaining = len_in; - ssize_t result = 0; - (void)result; - - /* Parse u64 magic IN [MAGIC_V1] */ - CHECK_REMAINING(8, truncated); - obj->magic = trunnel_ntohll(trunnel_get_uint64(ptr)); - remaining -= 8; ptr += 8; - if (! (obj->magic == MAGIC_V1)) - goto fail; - - /* Parse u64 format IN [T_GET_CONSISTENCY_PROOF_V1, T_GET_ENTRIES_V1, T_GET_PROOF_BY_HASH_V1] */ - CHECK_REMAINING(8, truncated); - obj->format = trunnel_ntohll(trunnel_get_uint64(ptr)); - remaining -= 8; ptr += 8; - if (! (obj->format == T_GET_CONSISTENCY_PROOF_V1 || obj->format == T_GET_ENTRIES_V1 || obj->format == T_GET_PROOF_BY_HASH_V1)) - goto fail; - - /* Parse union request[format] */ - switch (obj->format) { - - case T_GET_ENTRIES_V1: - - /* Parse struct req_get_entries_v1 request_get_entries */ - result = req_get_entries_v1_parse(&obj->request_get_entries, ptr, remaining); - if (result < 0) - goto relay_fail; - trunnel_assert((size_t)result <= remaining); - remaining -= result; ptr += result; - break; - - case T_GET_PROOF_BY_HASH_V1: - - /* Parse struct req_get_proof_by_hash_v1 request_get_proof_by_hash */ - result = req_get_proof_by_hash_v1_parse(&obj->request_get_proof_by_hash, ptr, remaining); - if (result < 0) - goto relay_fail; - trunnel_assert((size_t)result <= remaining); - remaining -= result; ptr += result; - break; - - case T_GET_CONSISTENCY_PROOF_V1: - - /* Parse struct req_get_consistency_proof_v1 request_get_consistency_proof */ - result = req_get_consistency_proof_v1_parse(&obj->request_get_consistency_proof, ptr, remaining); - if (result < 0) - goto relay_fail; - trunnel_assert((size_t)result <= remaining); - remaining -= result; ptr += result; - break; - - default: - goto fail; - break; - } - trunnel_assert(ptr + remaining == input + len_in); - return len_in - remaining; - - truncated: - return -2; - relay_fail: - trunnel_assert(result < 0); - return result; - fail: - result = -1; - return result; -} - -ssize_t -request_v1_parse(request_v1_t **output, const uint8_t *input, const size_t len_in) -{ - ssize_t result; - *output = request_v1_new(); - if (NULL == *output) - return -1; - result = request_v1_parse_into(*output, input, len_in); - if (result < 0) { - request_v1_free(*output); - *output = NULL; - } - return result; -} -signed_tree_head_v1_t * -signed_tree_head_v1_new(void) -{ - signed_tree_head_v1_t *val = trunnel_calloc(1, sizeof(signed_tree_head_v1_t)); - if (NULL == val) - return NULL; - val->magic = MAGIC_V1; - val->format = T_SIGNED_TREE_HEAD_V1; - return val; -} - -/** Release all storage held inside 'obj', but do not free 'obj'. - */ -static void -signed_tree_head_v1_clear(signed_tree_head_v1_t *obj) -{ - (void) obj; - { - - unsigned idx; - for (idx = 0; idx < TRUNNEL_DYNARRAY_LEN(&obj->signatures); ++idx) { - sigident_ed25519_free(TRUNNEL_DYNARRAY_GET(&obj->signatures, idx)); - } - } - TRUNNEL_DYNARRAY_WIPE(&obj->signatures); - TRUNNEL_DYNARRAY_CLEAR(&obj->signatures); -} - -void -signed_tree_head_v1_free(signed_tree_head_v1_t *obj) -{ - if (obj == NULL) - return; - signed_tree_head_v1_clear(obj); - trunnel_memwipe(obj, sizeof(signed_tree_head_v1_t)); - trunnel_free_(obj); -} - -uint64_t -signed_tree_head_v1_get_magic(const signed_tree_head_v1_t *inp) -{ - return inp->magic; -} -int -signed_tree_head_v1_set_magic(signed_tree_head_v1_t *inp, uint64_t val) -{ - if (! ((val == MAGIC_V1))) { - TRUNNEL_SET_ERROR_CODE(inp); - return -1; - } - inp->magic = val; - return 0; -} -uint64_t -signed_tree_head_v1_get_format(const signed_tree_head_v1_t *inp) -{ - return inp->format; -} -int -signed_tree_head_v1_set_format(signed_tree_head_v1_t *inp, uint64_t val) -{ - if (! ((val == T_SIGNED_TREE_HEAD_V1))) { - TRUNNEL_SET_ERROR_CODE(inp); - return -1; - } - inp->format = val; - return 0; -} -uint64_t -signed_tree_head_v1_get_timestamp(const signed_tree_head_v1_t *inp) -{ - return inp->timestamp; -} -int -signed_tree_head_v1_set_timestamp(signed_tree_head_v1_t *inp, uint64_t val) -{ - inp->timestamp = val; - return 0; -} -uint64_t -signed_tree_head_v1_get_tree_size(const signed_tree_head_v1_t *inp) -{ - return inp->tree_size; -} -int -signed_tree_head_v1_set_tree_size(signed_tree_head_v1_t *inp, uint64_t val) -{ - inp->tree_size = val; - return 0; -} -size_t -signed_tree_head_v1_getlen_root_hash(const signed_tree_head_v1_t *inp) -{ - (void)inp; return 32; -} - -uint8_t -signed_tree_head_v1_get_root_hash(signed_tree_head_v1_t *inp, size_t idx) -{ - trunnel_assert(idx < 32); - return inp->root_hash[idx]; -} - -uint8_t -signed_tree_head_v1_getconst_root_hash(const signed_tree_head_v1_t *inp, size_t idx) -{ - return signed_tree_head_v1_get_root_hash((signed_tree_head_v1_t*)inp, idx); -} -int -signed_tree_head_v1_set_root_hash(signed_tree_head_v1_t *inp, size_t idx, uint8_t elt) -{ - trunnel_assert(idx < 32); - inp->root_hash[idx] = elt; - return 0; -} - -uint8_t * -signed_tree_head_v1_getarray_root_hash(signed_tree_head_v1_t *inp) -{ - return inp->root_hash; -} -const uint8_t * -signed_tree_head_v1_getconstarray_root_hash(const signed_tree_head_v1_t *inp) -{ - return (const uint8_t *)signed_tree_head_v1_getarray_root_hash((signed_tree_head_v1_t*)inp); -} -uint64_t -signed_tree_head_v1_get_n_items(const signed_tree_head_v1_t *inp) -{ - return inp->n_items; -} -int -signed_tree_head_v1_set_n_items(signed_tree_head_v1_t *inp, uint64_t val) -{ - inp->n_items = val; - return 0; -} -size_t -signed_tree_head_v1_getlen_signatures(const signed_tree_head_v1_t *inp) -{ - return TRUNNEL_DYNARRAY_LEN(&inp->signatures); -} - -struct sigident_ed25519_st * -signed_tree_head_v1_get_signatures(signed_tree_head_v1_t *inp, size_t idx) -{ - return TRUNNEL_DYNARRAY_GET(&inp->signatures, idx); -} - - const struct sigident_ed25519_st * -signed_tree_head_v1_getconst_signatures(const signed_tree_head_v1_t *inp, size_t idx) -{ - return signed_tree_head_v1_get_signatures((signed_tree_head_v1_t*)inp, idx); -} -int -signed_tree_head_v1_set_signatures(signed_tree_head_v1_t *inp, size_t idx, struct sigident_ed25519_st * elt) -{ - sigident_ed25519_t *oldval = TRUNNEL_DYNARRAY_GET(&inp->signatures, idx); - if (oldval && oldval != elt) - sigident_ed25519_free(oldval); - return signed_tree_head_v1_set0_signatures(inp, idx, elt); -} -int -signed_tree_head_v1_set0_signatures(signed_tree_head_v1_t *inp, size_t idx, struct sigident_ed25519_st * elt) -{ - TRUNNEL_DYNARRAY_SET(&inp->signatures, idx, elt); - return 0; -} -int -signed_tree_head_v1_add_signatures(signed_tree_head_v1_t *inp, struct sigident_ed25519_st * elt) -{ -#if SIZE_MAX >= UINT64_MAX - if (inp->signatures.n_ == UINT64_MAX) - goto trunnel_alloc_failed; -#endif - TRUNNEL_DYNARRAY_ADD(struct sigident_ed25519_st *, &inp->signatures, elt, {}); - return 0; - trunnel_alloc_failed: - TRUNNEL_SET_ERROR_CODE(inp); - return -1; -} - -struct sigident_ed25519_st * * -signed_tree_head_v1_getarray_signatures(signed_tree_head_v1_t *inp) -{ - return inp->signatures.elts_; -} -const struct sigident_ed25519_st * const * -signed_tree_head_v1_getconstarray_signatures(const signed_tree_head_v1_t *inp) -{ - return (const struct sigident_ed25519_st * const *)signed_tree_head_v1_getarray_signatures((signed_tree_head_v1_t*)inp); -} -int -signed_tree_head_v1_setlen_signatures(signed_tree_head_v1_t *inp, size_t newlen) -{ - struct sigident_ed25519_st * *newptr; -#if UINT64_MAX < SIZE_MAX - if (newlen > UINT64_MAX) - goto trunnel_alloc_failed; -#endif - newptr = trunnel_dynarray_setlen(&inp->signatures.allocated_, - &inp->signatures.n_, inp->signatures.elts_, newlen, - sizeof(inp->signatures.elts_[0]), (trunnel_free_fn_t) sigident_ed25519_free, - &inp->trunnel_error_code_); - if (newlen != 0 && newptr == NULL) - goto trunnel_alloc_failed; - inp->signatures.elts_ = newptr; - return 0; - trunnel_alloc_failed: - TRUNNEL_SET_ERROR_CODE(inp); - return -1; -} -const char * -signed_tree_head_v1_check(const signed_tree_head_v1_t *obj) -{ - if (obj == NULL) - return "Object was NULL"; - if (obj->trunnel_error_code_) - return "A set function failed on this object"; - if (! (obj->magic == MAGIC_V1)) - return "Integer out of bounds"; - if (! (obj->format == T_SIGNED_TREE_HEAD_V1)) - return "Integer out of bounds"; - { - const char *msg; - - unsigned idx; - for (idx = 0; idx < TRUNNEL_DYNARRAY_LEN(&obj->signatures); ++idx) { - if (NULL != (msg = sigident_ed25519_check(TRUNNEL_DYNARRAY_GET(&obj->signatures, idx)))) - return msg; - } - } - if (TRUNNEL_DYNARRAY_LEN(&obj->signatures) != obj->n_items) - return "Length mismatch for signatures"; - return NULL; -} - -ssize_t -signed_tree_head_v1_encoded_len(const signed_tree_head_v1_t *obj) -{ - ssize_t result = 0; - - if (NULL != signed_tree_head_v1_check(obj)) - return -1; - - - /* Length of u64 magic IN [MAGIC_V1] */ - result += 8; - - /* Length of u64 format IN [T_SIGNED_TREE_HEAD_V1] */ - result += 8; - - /* Length of u64 timestamp */ - result += 8; - - /* Length of u64 tree_size */ - result += 8; - - /* Length of u8 root_hash[32] */ - result += 32; - - /* Length of u64 n_items */ - result += 8; - - /* Length of struct sigident_ed25519 signatures[n_items] */ - { - - unsigned idx; - for (idx = 0; idx < TRUNNEL_DYNARRAY_LEN(&obj->signatures); ++idx) { - result += sigident_ed25519_encoded_len(TRUNNEL_DYNARRAY_GET(&obj->signatures, idx)); - } - } - return result; -} -int -signed_tree_head_v1_clear_errors(signed_tree_head_v1_t *obj) -{ - int r = obj->trunnel_error_code_; - obj->trunnel_error_code_ = 0; - return r; -} -ssize_t -signed_tree_head_v1_encode(uint8_t *output, const size_t avail, const signed_tree_head_v1_t *obj) -{ - ssize_t result = 0; - size_t written = 0; - uint8_t *ptr = output; - const char *msg; -#ifdef TRUNNEL_CHECK_ENCODED_LEN - const ssize_t encoded_len = signed_tree_head_v1_encoded_len(obj); -#endif - - if (NULL != (msg = signed_tree_head_v1_check(obj))) - goto check_failed; - -#ifdef TRUNNEL_CHECK_ENCODED_LEN - trunnel_assert(encoded_len >= 0); -#endif - - /* Encode u64 magic IN [MAGIC_V1] */ - trunnel_assert(written <= avail); - if (avail - written < 8) - goto truncated; - trunnel_set_uint64(ptr, trunnel_htonll(obj->magic)); - written += 8; ptr += 8; - - /* Encode u64 format IN [T_SIGNED_TREE_HEAD_V1] */ - trunnel_assert(written <= avail); - if (avail - written < 8) - goto truncated; - trunnel_set_uint64(ptr, trunnel_htonll(obj->format)); - written += 8; ptr += 8; - - /* Encode u64 timestamp */ - trunnel_assert(written <= avail); - if (avail - written < 8) - goto truncated; - trunnel_set_uint64(ptr, trunnel_htonll(obj->timestamp)); - written += 8; ptr += 8; - - /* Encode u64 tree_size */ - trunnel_assert(written <= avail); - if (avail - written < 8) - goto truncated; - trunnel_set_uint64(ptr, trunnel_htonll(obj->tree_size)); - written += 8; ptr += 8; - - /* Encode u8 root_hash[32] */ - trunnel_assert(written <= avail); - if (avail - written < 32) - goto truncated; - memcpy(ptr, obj->root_hash, 32); - written += 32; ptr += 32; - - /* Encode u64 n_items */ - trunnel_assert(written <= avail); - if (avail - written < 8) - goto truncated; - trunnel_set_uint64(ptr, trunnel_htonll(obj->n_items)); - written += 8; ptr += 8; - - /* Encode struct sigident_ed25519 signatures[n_items] */ - { - - unsigned idx; - for (idx = 0; idx < TRUNNEL_DYNARRAY_LEN(&obj->signatures); ++idx) { - trunnel_assert(written <= avail); - result = sigident_ed25519_encode(ptr, avail - written, TRUNNEL_DYNARRAY_GET(&obj->signatures, idx)); - if (result < 0) - goto fail; /* XXXXXXX !*/ - written += result; ptr += result; - } - } - - - trunnel_assert(ptr == output + written); -#ifdef TRUNNEL_CHECK_ENCODED_LEN - { - trunnel_assert(encoded_len >= 0); - trunnel_assert((size_t)encoded_len == written); - } - -#endif - - return written; - - truncated: - result = -2; - goto fail; - check_failed: - (void)msg; - result = -1; - goto fail; - fail: - trunnel_assert(result < 0); - return result; -} - -/** As signed_tree_head_v1_parse(), but do not allocate the output - * object. - */ -static ssize_t -signed_tree_head_v1_parse_into(signed_tree_head_v1_t *obj, const uint8_t *input, const size_t len_in) -{ - const uint8_t *ptr = input; - size_t remaining = len_in; - ssize_t result = 0; - (void)result; - - /* Parse u64 magic IN [MAGIC_V1] */ - CHECK_REMAINING(8, truncated); - obj->magic = trunnel_ntohll(trunnel_get_uint64(ptr)); - remaining -= 8; ptr += 8; - if (! (obj->magic == MAGIC_V1)) - goto fail; - - /* Parse u64 format IN [T_SIGNED_TREE_HEAD_V1] */ - CHECK_REMAINING(8, truncated); - obj->format = trunnel_ntohll(trunnel_get_uint64(ptr)); - remaining -= 8; ptr += 8; - if (! (obj->format == T_SIGNED_TREE_HEAD_V1)) - goto fail; - - /* Parse u64 timestamp */ - CHECK_REMAINING(8, truncated); - obj->timestamp = trunnel_ntohll(trunnel_get_uint64(ptr)); - remaining -= 8; ptr += 8; - - /* Parse u64 tree_size */ - CHECK_REMAINING(8, truncated); - obj->tree_size = trunnel_ntohll(trunnel_get_uint64(ptr)); - remaining -= 8; ptr += 8; - - /* Parse u8 root_hash[32] */ - CHECK_REMAINING(32, truncated); - memcpy(obj->root_hash, ptr, 32); - remaining -= 32; ptr += 32; - - /* Parse u64 n_items */ - CHECK_REMAINING(8, truncated); - obj->n_items = trunnel_ntohll(trunnel_get_uint64(ptr)); - remaining -= 8; ptr += 8; - - /* Parse struct sigident_ed25519 signatures[n_items] */ - TRUNNEL_DYNARRAY_EXPAND(sigident_ed25519_t *, &obj->signatures, obj->n_items, {}); - { - sigident_ed25519_t * elt; - unsigned idx; - for (idx = 0; idx < obj->n_items; ++idx) { - result = sigident_ed25519_parse(&elt, ptr, remaining); - if (result < 0) - goto relay_fail; - trunnel_assert((size_t)result <= remaining); - remaining -= result; ptr += result; - TRUNNEL_DYNARRAY_ADD(sigident_ed25519_t *, &obj->signatures, elt, {sigident_ed25519_free(elt);}); - } - } - trunnel_assert(ptr + remaining == input + len_in); - return len_in - remaining; - - truncated: - return -2; - relay_fail: - trunnel_assert(result < 0); - return result; - trunnel_alloc_failed: - return -1; - fail: - result = -1; - return result; -} - -ssize_t -signed_tree_head_v1_parse(signed_tree_head_v1_t **output, const uint8_t *input, const size_t len_in) -{ - ssize_t result; - *output = signed_tree_head_v1_new(); - if (NULL == *output) - return -1; - result = signed_tree_head_v1_parse_into(*output, input, len_in); - if (result < 0) { - signed_tree_head_v1_free(*output); - *output = NULL; - } - return result; -} diff --git a/trunnel/stfe.h b/trunnel/stfe.h deleted file mode 100644 index 26ffb09..0000000 --- a/trunnel/stfe.h +++ /dev/null @@ -1,1235 +0,0 @@ -/* stfe.h -- generated by Trunnel v1.5.3. - * https://gitweb.torproject.org/trunnel.git - * You probably shouldn't edit this file. - */ -#ifndef TRUNNEL_STFE_H -#define TRUNNEL_STFE_H - -#include -#include "trunnel.h" - -#define MAGIC_V1 6004501466958485041 -#define T_GET_ENTRIES_V1 1 -#define T_GET_PROOF_BY_HASH_V1 2 -#define T_GET_CONSISTENCY_PROOF_V1 3 -#define T_ENTRIES_V1 4 -#define T_INCLUSION_PROOF_V1 5 -#define T_CONSISTENCY_PROOF_V1 6 -#define T_SIGNED_TREE_HEAD_V1 7 -#if !defined(TRUNNEL_OPAQUE) && !defined(TRUNNEL_OPAQUE_HASH) -struct hash_st { - uint8_t hash[32]; - uint8_t trunnel_error_code_; -}; -#endif -typedef struct hash_st hash_t; -#if !defined(TRUNNEL_OPAQUE) && !defined(TRUNNEL_OPAQUE_REQ_GET_CONSISTENCY_PROOF_V1) -struct req_get_consistency_proof_v1_st { - uint64_t old_size; - uint64_t new_size; - uint8_t trunnel_error_code_; -}; -#endif -typedef struct req_get_consistency_proof_v1_st req_get_consistency_proof_v1_t; -#if !defined(TRUNNEL_OPAQUE) && !defined(TRUNNEL_OPAQUE_REQ_GET_ENTRIES_V1) -struct req_get_entries_v1_st { - uint64_t start_size; - uint64_t end_size; - uint8_t trunnel_error_code_; -}; -#endif -typedef struct req_get_entries_v1_st req_get_entries_v1_t; -#if !defined(TRUNNEL_OPAQUE) && !defined(TRUNNEL_OPAQUE_REQ_GET_PROOF_BY_HASH_V1) -struct req_get_proof_by_hash_v1_st { - uint64_t tree_size; - uint8_t leaf_hash[32]; - uint8_t trunnel_error_code_; -}; -#endif -typedef struct req_get_proof_by_hash_v1_st req_get_proof_by_hash_v1_t; -#if !defined(TRUNNEL_OPAQUE) && !defined(TRUNNEL_OPAQUE_SIGIDENT_ED25519) -struct sigident_ed25519_st { - uint8_t signature[64]; - uint8_t identifier[32]; - uint8_t trunnel_error_code_; -}; -#endif -typedef struct sigident_ed25519_st sigident_ed25519_t; -#if !defined(TRUNNEL_OPAQUE) && !defined(TRUNNEL_OPAQUE_SIGNED_CHECKSUM32_ED25519) -struct signed_checksum32_ed25519_st { - uint8_t checksum[32]; - uint64_t length; - TRUNNEL_DYNARRAY_HEAD(, uint8_t) identifier; - uint8_t signature[64]; - uint8_t namespace[32]; - uint8_t trunnel_error_code_; -}; -#endif -typedef struct signed_checksum32_ed25519_st signed_checksum32_ed25519_t; -#if !defined(TRUNNEL_OPAQUE) && !defined(TRUNNEL_OPAQUE_CONSISTENCY_PROOF_V1) -struct consistency_proof_v1_st { - uint64_t magic; - uint64_t format; - uint8_t identifier[32]; - uint64_t old_size; - uint64_t new_size; - uint64_t n_items; - TRUNNEL_DYNARRAY_HEAD(, struct hash_st *) hashes; - uint8_t trunnel_error_code_; -}; -#endif -typedef struct consistency_proof_v1_st consistency_proof_v1_t; -#if !defined(TRUNNEL_OPAQUE) && !defined(TRUNNEL_OPAQUE_ENTRIES_V1) -struct entries_v1_st { - uint64_t magic; - uint64_t format; - uint64_t n_items; - TRUNNEL_DYNARRAY_HEAD(, struct signed_checksum32_ed25519_st *) checksums; - uint8_t trunnel_error_code_; -}; -#endif -typedef struct entries_v1_st entries_v1_t; -#if !defined(TRUNNEL_OPAQUE) && !defined(TRUNNEL_OPAQUE_INCLUSION_PROOF_V1) -struct inclusion_proof_v1_st { - uint64_t magic; - uint64_t format; - uint8_t identifier[32]; - uint64_t tree_size; - uint64_t leaf_index; - uint64_t n_items; - TRUNNEL_DYNARRAY_HEAD(, struct hash_st *) hashes; - uint8_t trunnel_error_code_; -}; -#endif -typedef struct inclusion_proof_v1_st inclusion_proof_v1_t; -#if !defined(TRUNNEL_OPAQUE) && !defined(TRUNNEL_OPAQUE_REQUEST_V1) -struct request_v1_st { - uint64_t magic; - uint64_t format; - struct req_get_entries_v1_st *request_get_entries; - struct req_get_proof_by_hash_v1_st *request_get_proof_by_hash; - struct req_get_consistency_proof_v1_st *request_get_consistency_proof; - uint8_t trunnel_error_code_; -}; -#endif -typedef struct request_v1_st request_v1_t; -#if !defined(TRUNNEL_OPAQUE) && !defined(TRUNNEL_OPAQUE_SIGNED_TREE_HEAD_V1) -struct signed_tree_head_v1_st { - uint64_t magic; - uint64_t format; - uint64_t timestamp; - uint64_t tree_size; - uint8_t root_hash[32]; - uint64_t n_items; - TRUNNEL_DYNARRAY_HEAD(, struct sigident_ed25519_st *) signatures; - uint8_t trunnel_error_code_; -}; -#endif -typedef struct signed_tree_head_v1_st signed_tree_head_v1_t; -/** Return a newly allocated hash with all elements set to zero. - */ -hash_t *hash_new(void); -/** Release all storage held by the hash in 'victim'. (Do nothing if - * 'victim' is NULL.) - */ -void hash_free(hash_t *victim); -/** Try to parse a hash from the buffer in 'input', using up to - * 'len_in' bytes from the input buffer. On success, return the number - * of bytes consumed and set *output to the newly allocated hash_t. On - * failure, return -2 if the input appears truncated, and -1 if the - * input is otherwise invalid. - */ -ssize_t hash_parse(hash_t **output, const uint8_t *input, const size_t len_in); -/** Return the number of bytes we expect to need to encode the hash in - * 'obj'. On failure, return a negative value. Note that this value - * may be an overestimate, and can even be an underestimate for - * certain unencodeable objects. - */ -ssize_t hash_encoded_len(const hash_t *obj); -/** Try to encode the hash from 'input' into the buffer at 'output', - * using up to 'avail' bytes of the output buffer. On success, return - * the number of bytes used. On failure, return -2 if the buffer was - * not long enough, and -1 if the input was invalid. - */ -ssize_t hash_encode(uint8_t *output, size_t avail, const hash_t *input); -/** Check whether the internal state of the hash in 'obj' is - * consistent. Return NULL if it is, and a short message if it is not. - */ -const char *hash_check(const hash_t *obj); -/** Clear any errors that were set on the object 'obj' by its setter - * functions. Return true iff errors were cleared. - */ -int hash_clear_errors(hash_t *obj); -/** Return the (constant) length of the array holding the hash field - * of the hash_t in 'inp'. - */ -size_t hash_getlen_hash(const hash_t *inp); -/** Return the element at position 'idx' of the fixed array field hash - * of the hash_t in 'inp'. - */ -uint8_t hash_get_hash(hash_t *inp, size_t idx); -/** As hash_get_hash, but take and return a const pointer - */ -uint8_t hash_getconst_hash(const hash_t *inp, size_t idx); -/** Change the element at position 'idx' of the fixed array field hash - * of the hash_t in 'inp', so that it will hold the value 'elt'. - */ -int hash_set_hash(hash_t *inp, size_t idx, uint8_t elt); -/** Return a pointer to the 32-element array field hash of 'inp'. - */ -uint8_t * hash_getarray_hash(hash_t *inp); -/** As hash_get_hash, but take and return a const pointer - */ -const uint8_t * hash_getconstarray_hash(const hash_t *inp); -/** Return a newly allocated req_get_consistency_proof_v1 with all - * elements set to zero. - */ -req_get_consistency_proof_v1_t *req_get_consistency_proof_v1_new(void); -/** Release all storage held by the req_get_consistency_proof_v1 in - * 'victim'. (Do nothing if 'victim' is NULL.) - */ -void req_get_consistency_proof_v1_free(req_get_consistency_proof_v1_t *victim); -/** Try to parse a req_get_consistency_proof_v1 from the buffer in - * 'input', using up to 'len_in' bytes from the input buffer. On - * success, return the number of bytes consumed and set *output to the - * newly allocated req_get_consistency_proof_v1_t. On failure, return - * -2 if the input appears truncated, and -1 if the input is otherwise - * invalid. - */ -ssize_t req_get_consistency_proof_v1_parse(req_get_consistency_proof_v1_t **output, const uint8_t *input, const size_t len_in); -/** Return the number of bytes we expect to need to encode the - * req_get_consistency_proof_v1 in 'obj'. On failure, return a - * negative value. Note that this value may be an overestimate, and - * can even be an underestimate for certain unencodeable objects. - */ -ssize_t req_get_consistency_proof_v1_encoded_len(const req_get_consistency_proof_v1_t *obj); -/** Try to encode the req_get_consistency_proof_v1 from 'input' into - * the buffer at 'output', using up to 'avail' bytes of the output - * buffer. On success, return the number of bytes used. On failure, - * return -2 if the buffer was not long enough, and -1 if the input - * was invalid. - */ -ssize_t req_get_consistency_proof_v1_encode(uint8_t *output, size_t avail, const req_get_consistency_proof_v1_t *input); -/** Check whether the internal state of the - * req_get_consistency_proof_v1 in 'obj' is consistent. Return NULL if - * it is, and a short message if it is not. - */ -const char *req_get_consistency_proof_v1_check(const req_get_consistency_proof_v1_t *obj); -/** Clear any errors that were set on the object 'obj' by its setter - * functions. Return true iff errors were cleared. - */ -int req_get_consistency_proof_v1_clear_errors(req_get_consistency_proof_v1_t *obj); -/** Return the value of the old_size field of the - * req_get_consistency_proof_v1_t in 'inp' - */ -uint64_t req_get_consistency_proof_v1_get_old_size(const req_get_consistency_proof_v1_t *inp); -/** Set the value of the old_size field of the - * req_get_consistency_proof_v1_t in 'inp' to 'val'. Return 0 on - * success; return -1 and set the error code on 'inp' on failure. - */ -int req_get_consistency_proof_v1_set_old_size(req_get_consistency_proof_v1_t *inp, uint64_t val); -/** Return the value of the new_size field of the - * req_get_consistency_proof_v1_t in 'inp' - */ -uint64_t req_get_consistency_proof_v1_get_new_size(const req_get_consistency_proof_v1_t *inp); -/** Set the value of the new_size field of the - * req_get_consistency_proof_v1_t in 'inp' to 'val'. Return 0 on - * success; return -1 and set the error code on 'inp' on failure. - */ -int req_get_consistency_proof_v1_set_new_size(req_get_consistency_proof_v1_t *inp, uint64_t val); -/** Return a newly allocated req_get_entries_v1 with all elements set - * to zero. - */ -req_get_entries_v1_t *req_get_entries_v1_new(void); -/** Release all storage held by the req_get_entries_v1 in 'victim'. - * (Do nothing if 'victim' is NULL.) - */ -void req_get_entries_v1_free(req_get_entries_v1_t *victim); -/** Try to parse a req_get_entries_v1 from the buffer in 'input', - * using up to 'len_in' bytes from the input buffer. On success, - * return the number of bytes consumed and set *output to the newly - * allocated req_get_entries_v1_t. On failure, return -2 if the input - * appears truncated, and -1 if the input is otherwise invalid. - */ -ssize_t req_get_entries_v1_parse(req_get_entries_v1_t **output, const uint8_t *input, const size_t len_in); -/** Return the number of bytes we expect to need to encode the - * req_get_entries_v1 in 'obj'. On failure, return a negative value. - * Note that this value may be an overestimate, and can even be an - * underestimate for certain unencodeable objects. - */ -ssize_t req_get_entries_v1_encoded_len(const req_get_entries_v1_t *obj); -/** Try to encode the req_get_entries_v1 from 'input' into the buffer - * at 'output', using up to 'avail' bytes of the output buffer. On - * success, return the number of bytes used. On failure, return -2 if - * the buffer was not long enough, and -1 if the input was invalid. - */ -ssize_t req_get_entries_v1_encode(uint8_t *output, size_t avail, const req_get_entries_v1_t *input); -/** Check whether the internal state of the req_get_entries_v1 in - * 'obj' is consistent. Return NULL if it is, and a short message if - * it is not. - */ -const char *req_get_entries_v1_check(const req_get_entries_v1_t *obj); -/** Clear any errors that were set on the object 'obj' by its setter - * functions. Return true iff errors were cleared. - */ -int req_get_entries_v1_clear_errors(req_get_entries_v1_t *obj); -/** Return the value of the start_size field of the - * req_get_entries_v1_t in 'inp' - */ -uint64_t req_get_entries_v1_get_start_size(const req_get_entries_v1_t *inp); -/** Set the value of the start_size field of the req_get_entries_v1_t - * in 'inp' to 'val'. Return 0 on success; return -1 and set the error - * code on 'inp' on failure. - */ -int req_get_entries_v1_set_start_size(req_get_entries_v1_t *inp, uint64_t val); -/** Return the value of the end_size field of the req_get_entries_v1_t - * in 'inp' - */ -uint64_t req_get_entries_v1_get_end_size(const req_get_entries_v1_t *inp); -/** Set the value of the end_size field of the req_get_entries_v1_t in - * 'inp' to 'val'. Return 0 on success; return -1 and set the error - * code on 'inp' on failure. - */ -int req_get_entries_v1_set_end_size(req_get_entries_v1_t *inp, uint64_t val); -/** Return a newly allocated req_get_proof_by_hash_v1 with all - * elements set to zero. - */ -req_get_proof_by_hash_v1_t *req_get_proof_by_hash_v1_new(void); -/** Release all storage held by the req_get_proof_by_hash_v1 in - * 'victim'. (Do nothing if 'victim' is NULL.) - */ -void req_get_proof_by_hash_v1_free(req_get_proof_by_hash_v1_t *victim); -/** Try to parse a req_get_proof_by_hash_v1 from the buffer in - * 'input', using up to 'len_in' bytes from the input buffer. On - * success, return the number of bytes consumed and set *output to the - * newly allocated req_get_proof_by_hash_v1_t. On failure, return -2 - * if the input appears truncated, and -1 if the input is otherwise - * invalid. - */ -ssize_t req_get_proof_by_hash_v1_parse(req_get_proof_by_hash_v1_t **output, const uint8_t *input, const size_t len_in); -/** Return the number of bytes we expect to need to encode the - * req_get_proof_by_hash_v1 in 'obj'. On failure, return a negative - * value. Note that this value may be an overestimate, and can even be - * an underestimate for certain unencodeable objects. - */ -ssize_t req_get_proof_by_hash_v1_encoded_len(const req_get_proof_by_hash_v1_t *obj); -/** Try to encode the req_get_proof_by_hash_v1 from 'input' into the - * buffer at 'output', using up to 'avail' bytes of the output buffer. - * On success, return the number of bytes used. On failure, return -2 - * if the buffer was not long enough, and -1 if the input was invalid. - */ -ssize_t req_get_proof_by_hash_v1_encode(uint8_t *output, size_t avail, const req_get_proof_by_hash_v1_t *input); -/** Check whether the internal state of the req_get_proof_by_hash_v1 - * in 'obj' is consistent. Return NULL if it is, and a short message - * if it is not. - */ -const char *req_get_proof_by_hash_v1_check(const req_get_proof_by_hash_v1_t *obj); -/** Clear any errors that were set on the object 'obj' by its setter - * functions. Return true iff errors were cleared. - */ -int req_get_proof_by_hash_v1_clear_errors(req_get_proof_by_hash_v1_t *obj); -/** Return the value of the tree_size field of the - * req_get_proof_by_hash_v1_t in 'inp' - */ -uint64_t req_get_proof_by_hash_v1_get_tree_size(const req_get_proof_by_hash_v1_t *inp); -/** Set the value of the tree_size field of the - * req_get_proof_by_hash_v1_t in 'inp' to 'val'. Return 0 on success; - * return -1 and set the error code on 'inp' on failure. - */ -int req_get_proof_by_hash_v1_set_tree_size(req_get_proof_by_hash_v1_t *inp, uint64_t val); -/** Return the (constant) length of the array holding the leaf_hash - * field of the req_get_proof_by_hash_v1_t in 'inp'. - */ -size_t req_get_proof_by_hash_v1_getlen_leaf_hash(const req_get_proof_by_hash_v1_t *inp); -/** Return the element at position 'idx' of the fixed array field - * leaf_hash of the req_get_proof_by_hash_v1_t in 'inp'. - */ -uint8_t req_get_proof_by_hash_v1_get_leaf_hash(req_get_proof_by_hash_v1_t *inp, size_t idx); -/** As req_get_proof_by_hash_v1_get_leaf_hash, but take and return a - * const pointer - */ -uint8_t req_get_proof_by_hash_v1_getconst_leaf_hash(const req_get_proof_by_hash_v1_t *inp, size_t idx); -/** Change the element at position 'idx' of the fixed array field - * leaf_hash of the req_get_proof_by_hash_v1_t in 'inp', so that it - * will hold the value 'elt'. - */ -int req_get_proof_by_hash_v1_set_leaf_hash(req_get_proof_by_hash_v1_t *inp, size_t idx, uint8_t elt); -/** Return a pointer to the 32-element array field leaf_hash of 'inp'. - */ -uint8_t * req_get_proof_by_hash_v1_getarray_leaf_hash(req_get_proof_by_hash_v1_t *inp); -/** As req_get_proof_by_hash_v1_get_leaf_hash, but take and return a - * const pointer - */ -const uint8_t * req_get_proof_by_hash_v1_getconstarray_leaf_hash(const req_get_proof_by_hash_v1_t *inp); -/** Return a newly allocated sigident_ed25519 with all elements set to - * zero. - */ -sigident_ed25519_t *sigident_ed25519_new(void); -/** Release all storage held by the sigident_ed25519 in 'victim'. (Do - * nothing if 'victim' is NULL.) - */ -void sigident_ed25519_free(sigident_ed25519_t *victim); -/** Try to parse a sigident_ed25519 from the buffer in 'input', using - * up to 'len_in' bytes from the input buffer. On success, return the - * number of bytes consumed and set *output to the newly allocated - * sigident_ed25519_t. On failure, return -2 if the input appears - * truncated, and -1 if the input is otherwise invalid. - */ -ssize_t sigident_ed25519_parse(sigident_ed25519_t **output, const uint8_t *input, const size_t len_in); -/** Return the number of bytes we expect to need to encode the - * sigident_ed25519 in 'obj'. On failure, return a negative value. - * Note that this value may be an overestimate, and can even be an - * underestimate for certain unencodeable objects. - */ -ssize_t sigident_ed25519_encoded_len(const sigident_ed25519_t *obj); -/** Try to encode the sigident_ed25519 from 'input' into the buffer at - * 'output', using up to 'avail' bytes of the output buffer. On - * success, return the number of bytes used. On failure, return -2 if - * the buffer was not long enough, and -1 if the input was invalid. - */ -ssize_t sigident_ed25519_encode(uint8_t *output, size_t avail, const sigident_ed25519_t *input); -/** Check whether the internal state of the sigident_ed25519 in 'obj' - * is consistent. Return NULL if it is, and a short message if it is - * not. - */ -const char *sigident_ed25519_check(const sigident_ed25519_t *obj); -/** Clear any errors that were set on the object 'obj' by its setter - * functions. Return true iff errors were cleared. - */ -int sigident_ed25519_clear_errors(sigident_ed25519_t *obj); -/** Return the (constant) length of the array holding the signature - * field of the sigident_ed25519_t in 'inp'. - */ -size_t sigident_ed25519_getlen_signature(const sigident_ed25519_t *inp); -/** Return the element at position 'idx' of the fixed array field - * signature of the sigident_ed25519_t in 'inp'. - */ -uint8_t sigident_ed25519_get_signature(sigident_ed25519_t *inp, size_t idx); -/** As sigident_ed25519_get_signature, but take and return a const - * pointer - */ -uint8_t sigident_ed25519_getconst_signature(const sigident_ed25519_t *inp, size_t idx); -/** Change the element at position 'idx' of the fixed array field - * signature of the sigident_ed25519_t in 'inp', so that it will hold - * the value 'elt'. - */ -int sigident_ed25519_set_signature(sigident_ed25519_t *inp, size_t idx, uint8_t elt); -/** Return a pointer to the 64-element array field signature of 'inp'. - */ -uint8_t * sigident_ed25519_getarray_signature(sigident_ed25519_t *inp); -/** As sigident_ed25519_get_signature, but take and return a const - * pointer - */ -const uint8_t * sigident_ed25519_getconstarray_signature(const sigident_ed25519_t *inp); -/** Return the (constant) length of the array holding the identifier - * field of the sigident_ed25519_t in 'inp'. - */ -size_t sigident_ed25519_getlen_identifier(const sigident_ed25519_t *inp); -/** Return the element at position 'idx' of the fixed array field - * identifier of the sigident_ed25519_t in 'inp'. - */ -uint8_t sigident_ed25519_get_identifier(sigident_ed25519_t *inp, size_t idx); -/** As sigident_ed25519_get_identifier, but take and return a const - * pointer - */ -uint8_t sigident_ed25519_getconst_identifier(const sigident_ed25519_t *inp, size_t idx); -/** Change the element at position 'idx' of the fixed array field - * identifier of the sigident_ed25519_t in 'inp', so that it will hold - * the value 'elt'. - */ -int sigident_ed25519_set_identifier(sigident_ed25519_t *inp, size_t idx, uint8_t elt); -/** Return a pointer to the 32-element array field identifier of - * 'inp'. - */ -uint8_t * sigident_ed25519_getarray_identifier(sigident_ed25519_t *inp); -/** As sigident_ed25519_get_identifier, but take and return a const - * pointer - */ -const uint8_t * sigident_ed25519_getconstarray_identifier(const sigident_ed25519_t *inp); -/** Return a newly allocated signed_checksum32_ed25519 with all - * elements set to zero. - */ -signed_checksum32_ed25519_t *signed_checksum32_ed25519_new(void); -/** Release all storage held by the signed_checksum32_ed25519 in - * 'victim'. (Do nothing if 'victim' is NULL.) - */ -void signed_checksum32_ed25519_free(signed_checksum32_ed25519_t *victim); -/** Try to parse a signed_checksum32_ed25519 from the buffer in - * 'input', using up to 'len_in' bytes from the input buffer. On - * success, return the number of bytes consumed and set *output to the - * newly allocated signed_checksum32_ed25519_t. On failure, return -2 - * if the input appears truncated, and -1 if the input is otherwise - * invalid. - */ -ssize_t signed_checksum32_ed25519_parse(signed_checksum32_ed25519_t **output, const uint8_t *input, const size_t len_in); -/** Return the number of bytes we expect to need to encode the - * signed_checksum32_ed25519 in 'obj'. On failure, return a negative - * value. Note that this value may be an overestimate, and can even be - * an underestimate for certain unencodeable objects. - */ -ssize_t signed_checksum32_ed25519_encoded_len(const signed_checksum32_ed25519_t *obj); -/** Try to encode the signed_checksum32_ed25519 from 'input' into the - * buffer at 'output', using up to 'avail' bytes of the output buffer. - * On success, return the number of bytes used. On failure, return -2 - * if the buffer was not long enough, and -1 if the input was invalid. - */ -ssize_t signed_checksum32_ed25519_encode(uint8_t *output, size_t avail, const signed_checksum32_ed25519_t *input); -/** Check whether the internal state of the signed_checksum32_ed25519 - * in 'obj' is consistent. Return NULL if it is, and a short message - * if it is not. - */ -const char *signed_checksum32_ed25519_check(const signed_checksum32_ed25519_t *obj); -/** Clear any errors that were set on the object 'obj' by its setter - * functions. Return true iff errors were cleared. - */ -int signed_checksum32_ed25519_clear_errors(signed_checksum32_ed25519_t *obj); -/** Return the (constant) length of the array holding the checksum - * field of the signed_checksum32_ed25519_t in 'inp'. - */ -size_t signed_checksum32_ed25519_getlen_checksum(const signed_checksum32_ed25519_t *inp); -/** Return the element at position 'idx' of the fixed array field - * checksum of the signed_checksum32_ed25519_t in 'inp'. - */ -uint8_t signed_checksum32_ed25519_get_checksum(signed_checksum32_ed25519_t *inp, size_t idx); -/** As signed_checksum32_ed25519_get_checksum, but take and return a - * const pointer - */ -uint8_t signed_checksum32_ed25519_getconst_checksum(const signed_checksum32_ed25519_t *inp, size_t idx); -/** Change the element at position 'idx' of the fixed array field - * checksum of the signed_checksum32_ed25519_t in 'inp', so that it - * will hold the value 'elt'. - */ -int signed_checksum32_ed25519_set_checksum(signed_checksum32_ed25519_t *inp, size_t idx, uint8_t elt); -/** Return a pointer to the 32-element array field checksum of 'inp'. - */ -uint8_t * signed_checksum32_ed25519_getarray_checksum(signed_checksum32_ed25519_t *inp); -/** As signed_checksum32_ed25519_get_checksum, but take and return a - * const pointer - */ -const uint8_t * signed_checksum32_ed25519_getconstarray_checksum(const signed_checksum32_ed25519_t *inp); -/** Return the value of the length field of the - * signed_checksum32_ed25519_t in 'inp' - */ -uint64_t signed_checksum32_ed25519_get_length(const signed_checksum32_ed25519_t *inp); -/** Set the value of the length field of the - * signed_checksum32_ed25519_t in 'inp' to 'val'. Return 0 on success; - * return -1 and set the error code on 'inp' on failure. - */ -int signed_checksum32_ed25519_set_length(signed_checksum32_ed25519_t *inp, uint64_t val); -/** Return the length of the dynamic array holding the identifier - * field of the signed_checksum32_ed25519_t in 'inp'. - */ -size_t signed_checksum32_ed25519_getlen_identifier(const signed_checksum32_ed25519_t *inp); -/** Return the element at position 'idx' of the dynamic array field - * identifier of the signed_checksum32_ed25519_t in 'inp'. - */ -uint8_t signed_checksum32_ed25519_get_identifier(signed_checksum32_ed25519_t *inp, size_t idx); -/** As signed_checksum32_ed25519_get_identifier, but take and return a - * const pointer - */ -uint8_t signed_checksum32_ed25519_getconst_identifier(const signed_checksum32_ed25519_t *inp, size_t idx); -/** Change the element at position 'idx' of the dynamic array field - * identifier of the signed_checksum32_ed25519_t in 'inp', so that it - * will hold the value 'elt'. - */ -int signed_checksum32_ed25519_set_identifier(signed_checksum32_ed25519_t *inp, size_t idx, uint8_t elt); -/** Append a new element 'elt' to the dynamic array field identifier - * of the signed_checksum32_ed25519_t in 'inp'. - */ -int signed_checksum32_ed25519_add_identifier(signed_checksum32_ed25519_t *inp, uint8_t elt); -/** Return a pointer to the variable-length array field identifier of - * 'inp'. - */ -uint8_t * signed_checksum32_ed25519_getarray_identifier(signed_checksum32_ed25519_t *inp); -/** As signed_checksum32_ed25519_get_identifier, but take and return a - * const pointer - */ -const uint8_t * signed_checksum32_ed25519_getconstarray_identifier(const signed_checksum32_ed25519_t *inp); -/** Change the length of the variable-length array field identifier of - * 'inp' to 'newlen'.Fill extra elements with 0. Return 0 on success; - * return -1 and set the error code on 'inp' on failure. - */ -int signed_checksum32_ed25519_setlen_identifier(signed_checksum32_ed25519_t *inp, size_t newlen); -/** Return the (constant) length of the array holding the signature - * field of the signed_checksum32_ed25519_t in 'inp'. - */ -size_t signed_checksum32_ed25519_getlen_signature(const signed_checksum32_ed25519_t *inp); -/** Return the element at position 'idx' of the fixed array field - * signature of the signed_checksum32_ed25519_t in 'inp'. - */ -uint8_t signed_checksum32_ed25519_get_signature(signed_checksum32_ed25519_t *inp, size_t idx); -/** As signed_checksum32_ed25519_get_signature, but take and return a - * const pointer - */ -uint8_t signed_checksum32_ed25519_getconst_signature(const signed_checksum32_ed25519_t *inp, size_t idx); -/** Change the element at position 'idx' of the fixed array field - * signature of the signed_checksum32_ed25519_t in 'inp', so that it - * will hold the value 'elt'. - */ -int signed_checksum32_ed25519_set_signature(signed_checksum32_ed25519_t *inp, size_t idx, uint8_t elt); -/** Return a pointer to the 64-element array field signature of 'inp'. - */ -uint8_t * signed_checksum32_ed25519_getarray_signature(signed_checksum32_ed25519_t *inp); -/** As signed_checksum32_ed25519_get_signature, but take and return a - * const pointer - */ -const uint8_t * signed_checksum32_ed25519_getconstarray_signature(const signed_checksum32_ed25519_t *inp); -/** Return the (constant) length of the array holding the namespace - * field of the signed_checksum32_ed25519_t in 'inp'. - */ -size_t signed_checksum32_ed25519_getlen_namespace(const signed_checksum32_ed25519_t *inp); -/** Return the element at position 'idx' of the fixed array field - * namespace of the signed_checksum32_ed25519_t in 'inp'. - */ -uint8_t signed_checksum32_ed25519_get_namespace(signed_checksum32_ed25519_t *inp, size_t idx); -/** As signed_checksum32_ed25519_get_namespace, but take and return a - * const pointer - */ -uint8_t signed_checksum32_ed25519_getconst_namespace(const signed_checksum32_ed25519_t *inp, size_t idx); -/** Change the element at position 'idx' of the fixed array field - * namespace of the signed_checksum32_ed25519_t in 'inp', so that it - * will hold the value 'elt'. - */ -int signed_checksum32_ed25519_set_namespace(signed_checksum32_ed25519_t *inp, size_t idx, uint8_t elt); -/** Return a pointer to the 32-element array field namespace of 'inp'. - */ -uint8_t * signed_checksum32_ed25519_getarray_namespace(signed_checksum32_ed25519_t *inp); -/** As signed_checksum32_ed25519_get_namespace, but take and return a - * const pointer - */ -const uint8_t * signed_checksum32_ed25519_getconstarray_namespace(const signed_checksum32_ed25519_t *inp); -/** Return a newly allocated consistency_proof_v1 with all elements - * set to zero. - */ -consistency_proof_v1_t *consistency_proof_v1_new(void); -/** Release all storage held by the consistency_proof_v1 in 'victim'. - * (Do nothing if 'victim' is NULL.) - */ -void consistency_proof_v1_free(consistency_proof_v1_t *victim); -/** Try to parse a consistency_proof_v1 from the buffer in 'input', - * using up to 'len_in' bytes from the input buffer. On success, - * return the number of bytes consumed and set *output to the newly - * allocated consistency_proof_v1_t. On failure, return -2 if the - * input appears truncated, and -1 if the input is otherwise invalid. - */ -ssize_t consistency_proof_v1_parse(consistency_proof_v1_t **output, const uint8_t *input, const size_t len_in); -/** Return the number of bytes we expect to need to encode the - * consistency_proof_v1 in 'obj'. On failure, return a negative value. - * Note that this value may be an overestimate, and can even be an - * underestimate for certain unencodeable objects. - */ -ssize_t consistency_proof_v1_encoded_len(const consistency_proof_v1_t *obj); -/** Try to encode the consistency_proof_v1 from 'input' into the - * buffer at 'output', using up to 'avail' bytes of the output buffer. - * On success, return the number of bytes used. On failure, return -2 - * if the buffer was not long enough, and -1 if the input was invalid. - */ -ssize_t consistency_proof_v1_encode(uint8_t *output, size_t avail, const consistency_proof_v1_t *input); -/** Check whether the internal state of the consistency_proof_v1 in - * 'obj' is consistent. Return NULL if it is, and a short message if - * it is not. - */ -const char *consistency_proof_v1_check(const consistency_proof_v1_t *obj); -/** Clear any errors that were set on the object 'obj' by its setter - * functions. Return true iff errors were cleared. - */ -int consistency_proof_v1_clear_errors(consistency_proof_v1_t *obj); -/** Return the value of the magic field of the consistency_proof_v1_t - * in 'inp' - */ -uint64_t consistency_proof_v1_get_magic(const consistency_proof_v1_t *inp); -/** Set the value of the magic field of the consistency_proof_v1_t in - * 'inp' to 'val'. Return 0 on success; return -1 and set the error - * code on 'inp' on failure. - */ -int consistency_proof_v1_set_magic(consistency_proof_v1_t *inp, uint64_t val); -/** Return the value of the format field of the consistency_proof_v1_t - * in 'inp' - */ -uint64_t consistency_proof_v1_get_format(const consistency_proof_v1_t *inp); -/** Set the value of the format field of the consistency_proof_v1_t in - * 'inp' to 'val'. Return 0 on success; return -1 and set the error - * code on 'inp' on failure. - */ -int consistency_proof_v1_set_format(consistency_proof_v1_t *inp, uint64_t val); -/** Return the (constant) length of the array holding the identifier - * field of the consistency_proof_v1_t in 'inp'. - */ -size_t consistency_proof_v1_getlen_identifier(const consistency_proof_v1_t *inp); -/** Return the element at position 'idx' of the fixed array field - * identifier of the consistency_proof_v1_t in 'inp'. - */ -uint8_t consistency_proof_v1_get_identifier(consistency_proof_v1_t *inp, size_t idx); -/** As consistency_proof_v1_get_identifier, but take and return a - * const pointer - */ -uint8_t consistency_proof_v1_getconst_identifier(const consistency_proof_v1_t *inp, size_t idx); -/** Change the element at position 'idx' of the fixed array field - * identifier of the consistency_proof_v1_t in 'inp', so that it will - * hold the value 'elt'. - */ -int consistency_proof_v1_set_identifier(consistency_proof_v1_t *inp, size_t idx, uint8_t elt); -/** Return a pointer to the 32-element array field identifier of - * 'inp'. - */ -uint8_t * consistency_proof_v1_getarray_identifier(consistency_proof_v1_t *inp); -/** As consistency_proof_v1_get_identifier, but take and return a - * const pointer - */ -const uint8_t * consistency_proof_v1_getconstarray_identifier(const consistency_proof_v1_t *inp); -/** Return the value of the old_size field of the - * consistency_proof_v1_t in 'inp' - */ -uint64_t consistency_proof_v1_get_old_size(const consistency_proof_v1_t *inp); -/** Set the value of the old_size field of the consistency_proof_v1_t - * in 'inp' to 'val'. Return 0 on success; return -1 and set the error - * code on 'inp' on failure. - */ -int consistency_proof_v1_set_old_size(consistency_proof_v1_t *inp, uint64_t val); -/** Return the value of the new_size field of the - * consistency_proof_v1_t in 'inp' - */ -uint64_t consistency_proof_v1_get_new_size(const consistency_proof_v1_t *inp); -/** Set the value of the new_size field of the consistency_proof_v1_t - * in 'inp' to 'val'. Return 0 on success; return -1 and set the error - * code on 'inp' on failure. - */ -int consistency_proof_v1_set_new_size(consistency_proof_v1_t *inp, uint64_t val); -/** Return the value of the n_items field of the - * consistency_proof_v1_t in 'inp' - */ -uint64_t consistency_proof_v1_get_n_items(const consistency_proof_v1_t *inp); -/** Set the value of the n_items field of the consistency_proof_v1_t - * in 'inp' to 'val'. Return 0 on success; return -1 and set the error - * code on 'inp' on failure. - */ -int consistency_proof_v1_set_n_items(consistency_proof_v1_t *inp, uint64_t val); -/** Return the length of the dynamic array holding the hashes field of - * the consistency_proof_v1_t in 'inp'. - */ -size_t consistency_proof_v1_getlen_hashes(const consistency_proof_v1_t *inp); -/** Return the element at position 'idx' of the dynamic array field - * hashes of the consistency_proof_v1_t in 'inp'. - */ -struct hash_st * consistency_proof_v1_get_hashes(consistency_proof_v1_t *inp, size_t idx); -/** As consistency_proof_v1_get_hashes, but take and return a const - * pointer - */ - const struct hash_st * consistency_proof_v1_getconst_hashes(const consistency_proof_v1_t *inp, size_t idx); -/** Change the element at position 'idx' of the dynamic array field - * hashes of the consistency_proof_v1_t in 'inp', so that it will hold - * the value 'elt'. Free the previous value, if any. - */ -int consistency_proof_v1_set_hashes(consistency_proof_v1_t *inp, size_t idx, struct hash_st * elt); -/** As consistency_proof_v1_set_hashes, but does not free the previous - * value. - */ -int consistency_proof_v1_set0_hashes(consistency_proof_v1_t *inp, size_t idx, struct hash_st * elt); -/** Append a new element 'elt' to the dynamic array field hashes of - * the consistency_proof_v1_t in 'inp'. - */ -int consistency_proof_v1_add_hashes(consistency_proof_v1_t *inp, struct hash_st * elt); -/** Return a pointer to the variable-length array field hashes of - * 'inp'. - */ -struct hash_st * * consistency_proof_v1_getarray_hashes(consistency_proof_v1_t *inp); -/** As consistency_proof_v1_get_hashes, but take and return a const - * pointer - */ -const struct hash_st * const * consistency_proof_v1_getconstarray_hashes(const consistency_proof_v1_t *inp); -/** Change the length of the variable-length array field hashes of - * 'inp' to 'newlen'.Fill extra elements with NULL; free removed - * elements. Return 0 on success; return -1 and set the error code on - * 'inp' on failure. - */ -int consistency_proof_v1_setlen_hashes(consistency_proof_v1_t *inp, size_t newlen); -/** Return a newly allocated entries_v1 with all elements set to zero. - */ -entries_v1_t *entries_v1_new(void); -/** Release all storage held by the entries_v1 in 'victim'. (Do - * nothing if 'victim' is NULL.) - */ -void entries_v1_free(entries_v1_t *victim); -/** Try to parse a entries_v1 from the buffer in 'input', using up to - * 'len_in' bytes from the input buffer. On success, return the number - * of bytes consumed and set *output to the newly allocated - * entries_v1_t. On failure, return -2 if the input appears truncated, - * and -1 if the input is otherwise invalid. - */ -ssize_t entries_v1_parse(entries_v1_t **output, const uint8_t *input, const size_t len_in); -/** Return the number of bytes we expect to need to encode the - * entries_v1 in 'obj'. On failure, return a negative value. Note that - * this value may be an overestimate, and can even be an underestimate - * for certain unencodeable objects. - */ -ssize_t entries_v1_encoded_len(const entries_v1_t *obj); -/** Try to encode the entries_v1 from 'input' into the buffer at - * 'output', using up to 'avail' bytes of the output buffer. On - * success, return the number of bytes used. On failure, return -2 if - * the buffer was not long enough, and -1 if the input was invalid. - */ -ssize_t entries_v1_encode(uint8_t *output, size_t avail, const entries_v1_t *input); -/** Check whether the internal state of the entries_v1 in 'obj' is - * consistent. Return NULL if it is, and a short message if it is not. - */ -const char *entries_v1_check(const entries_v1_t *obj); -/** Clear any errors that were set on the object 'obj' by its setter - * functions. Return true iff errors were cleared. - */ -int entries_v1_clear_errors(entries_v1_t *obj); -/** Return the value of the magic field of the entries_v1_t in 'inp' - */ -uint64_t entries_v1_get_magic(const entries_v1_t *inp); -/** Set the value of the magic field of the entries_v1_t in 'inp' to - * 'val'. Return 0 on success; return -1 and set the error code on - * 'inp' on failure. - */ -int entries_v1_set_magic(entries_v1_t *inp, uint64_t val); -/** Return the value of the format field of the entries_v1_t in 'inp' - */ -uint64_t entries_v1_get_format(const entries_v1_t *inp); -/** Set the value of the format field of the entries_v1_t in 'inp' to - * 'val'. Return 0 on success; return -1 and set the error code on - * 'inp' on failure. - */ -int entries_v1_set_format(entries_v1_t *inp, uint64_t val); -/** Return the value of the n_items field of the entries_v1_t in 'inp' - */ -uint64_t entries_v1_get_n_items(const entries_v1_t *inp); -/** Set the value of the n_items field of the entries_v1_t in 'inp' to - * 'val'. Return 0 on success; return -1 and set the error code on - * 'inp' on failure. - */ -int entries_v1_set_n_items(entries_v1_t *inp, uint64_t val); -/** Return the length of the dynamic array holding the checksums field - * of the entries_v1_t in 'inp'. - */ -size_t entries_v1_getlen_checksums(const entries_v1_t *inp); -/** Return the element at position 'idx' of the dynamic array field - * checksums of the entries_v1_t in 'inp'. - */ -struct signed_checksum32_ed25519_st * entries_v1_get_checksums(entries_v1_t *inp, size_t idx); -/** As entries_v1_get_checksums, but take and return a const pointer - */ - const struct signed_checksum32_ed25519_st * entries_v1_getconst_checksums(const entries_v1_t *inp, size_t idx); -/** Change the element at position 'idx' of the dynamic array field - * checksums of the entries_v1_t in 'inp', so that it will hold the - * value 'elt'. Free the previous value, if any. - */ -int entries_v1_set_checksums(entries_v1_t *inp, size_t idx, struct signed_checksum32_ed25519_st * elt); -/** As entries_v1_set_checksums, but does not free the previous value. - */ -int entries_v1_set0_checksums(entries_v1_t *inp, size_t idx, struct signed_checksum32_ed25519_st * elt); -/** Append a new element 'elt' to the dynamic array field checksums of - * the entries_v1_t in 'inp'. - */ -int entries_v1_add_checksums(entries_v1_t *inp, struct signed_checksum32_ed25519_st * elt); -/** Return a pointer to the variable-length array field checksums of - * 'inp'. - */ -struct signed_checksum32_ed25519_st * * entries_v1_getarray_checksums(entries_v1_t *inp); -/** As entries_v1_get_checksums, but take and return a const pointer - */ -const struct signed_checksum32_ed25519_st * const * entries_v1_getconstarray_checksums(const entries_v1_t *inp); -/** Change the length of the variable-length array field checksums of - * 'inp' to 'newlen'.Fill extra elements with NULL; free removed - * elements. Return 0 on success; return -1 and set the error code on - * 'inp' on failure. - */ -int entries_v1_setlen_checksums(entries_v1_t *inp, size_t newlen); -/** Return a newly allocated inclusion_proof_v1 with all elements set - * to zero. - */ -inclusion_proof_v1_t *inclusion_proof_v1_new(void); -/** Release all storage held by the inclusion_proof_v1 in 'victim'. - * (Do nothing if 'victim' is NULL.) - */ -void inclusion_proof_v1_free(inclusion_proof_v1_t *victim); -/** Try to parse a inclusion_proof_v1 from the buffer in 'input', - * using up to 'len_in' bytes from the input buffer. On success, - * return the number of bytes consumed and set *output to the newly - * allocated inclusion_proof_v1_t. On failure, return -2 if the input - * appears truncated, and -1 if the input is otherwise invalid. - */ -ssize_t inclusion_proof_v1_parse(inclusion_proof_v1_t **output, const uint8_t *input, const size_t len_in); -/** Return the number of bytes we expect to need to encode the - * inclusion_proof_v1 in 'obj'. On failure, return a negative value. - * Note that this value may be an overestimate, and can even be an - * underestimate for certain unencodeable objects. - */ -ssize_t inclusion_proof_v1_encoded_len(const inclusion_proof_v1_t *obj); -/** Try to encode the inclusion_proof_v1 from 'input' into the buffer - * at 'output', using up to 'avail' bytes of the output buffer. On - * success, return the number of bytes used. On failure, return -2 if - * the buffer was not long enough, and -1 if the input was invalid. - */ -ssize_t inclusion_proof_v1_encode(uint8_t *output, size_t avail, const inclusion_proof_v1_t *input); -/** Check whether the internal state of the inclusion_proof_v1 in - * 'obj' is consistent. Return NULL if it is, and a short message if - * it is not. - */ -const char *inclusion_proof_v1_check(const inclusion_proof_v1_t *obj); -/** Clear any errors that were set on the object 'obj' by its setter - * functions. Return true iff errors were cleared. - */ -int inclusion_proof_v1_clear_errors(inclusion_proof_v1_t *obj); -/** Return the value of the magic field of the inclusion_proof_v1_t in - * 'inp' - */ -uint64_t inclusion_proof_v1_get_magic(const inclusion_proof_v1_t *inp); -/** Set the value of the magic field of the inclusion_proof_v1_t in - * 'inp' to 'val'. Return 0 on success; return -1 and set the error - * code on 'inp' on failure. - */ -int inclusion_proof_v1_set_magic(inclusion_proof_v1_t *inp, uint64_t val); -/** Return the value of the format field of the inclusion_proof_v1_t - * in 'inp' - */ -uint64_t inclusion_proof_v1_get_format(const inclusion_proof_v1_t *inp); -/** Set the value of the format field of the inclusion_proof_v1_t in - * 'inp' to 'val'. Return 0 on success; return -1 and set the error - * code on 'inp' on failure. - */ -int inclusion_proof_v1_set_format(inclusion_proof_v1_t *inp, uint64_t val); -/** Return the (constant) length of the array holding the identifier - * field of the inclusion_proof_v1_t in 'inp'. - */ -size_t inclusion_proof_v1_getlen_identifier(const inclusion_proof_v1_t *inp); -/** Return the element at position 'idx' of the fixed array field - * identifier of the inclusion_proof_v1_t in 'inp'. - */ -uint8_t inclusion_proof_v1_get_identifier(inclusion_proof_v1_t *inp, size_t idx); -/** As inclusion_proof_v1_get_identifier, but take and return a const - * pointer - */ -uint8_t inclusion_proof_v1_getconst_identifier(const inclusion_proof_v1_t *inp, size_t idx); -/** Change the element at position 'idx' of the fixed array field - * identifier of the inclusion_proof_v1_t in 'inp', so that it will - * hold the value 'elt'. - */ -int inclusion_proof_v1_set_identifier(inclusion_proof_v1_t *inp, size_t idx, uint8_t elt); -/** Return a pointer to the 32-element array field identifier of - * 'inp'. - */ -uint8_t * inclusion_proof_v1_getarray_identifier(inclusion_proof_v1_t *inp); -/** As inclusion_proof_v1_get_identifier, but take and return a const - * pointer - */ -const uint8_t * inclusion_proof_v1_getconstarray_identifier(const inclusion_proof_v1_t *inp); -/** Return the value of the tree_size field of the - * inclusion_proof_v1_t in 'inp' - */ -uint64_t inclusion_proof_v1_get_tree_size(const inclusion_proof_v1_t *inp); -/** Set the value of the tree_size field of the inclusion_proof_v1_t - * in 'inp' to 'val'. Return 0 on success; return -1 and set the error - * code on 'inp' on failure. - */ -int inclusion_proof_v1_set_tree_size(inclusion_proof_v1_t *inp, uint64_t val); -/** Return the value of the leaf_index field of the - * inclusion_proof_v1_t in 'inp' - */ -uint64_t inclusion_proof_v1_get_leaf_index(const inclusion_proof_v1_t *inp); -/** Set the value of the leaf_index field of the inclusion_proof_v1_t - * in 'inp' to 'val'. Return 0 on success; return -1 and set the error - * code on 'inp' on failure. - */ -int inclusion_proof_v1_set_leaf_index(inclusion_proof_v1_t *inp, uint64_t val); -/** Return the value of the n_items field of the inclusion_proof_v1_t - * in 'inp' - */ -uint64_t inclusion_proof_v1_get_n_items(const inclusion_proof_v1_t *inp); -/** Set the value of the n_items field of the inclusion_proof_v1_t in - * 'inp' to 'val'. Return 0 on success; return -1 and set the error - * code on 'inp' on failure. - */ -int inclusion_proof_v1_set_n_items(inclusion_proof_v1_t *inp, uint64_t val); -/** Return the length of the dynamic array holding the hashes field of - * the inclusion_proof_v1_t in 'inp'. - */ -size_t inclusion_proof_v1_getlen_hashes(const inclusion_proof_v1_t *inp); -/** Return the element at position 'idx' of the dynamic array field - * hashes of the inclusion_proof_v1_t in 'inp'. - */ -struct hash_st * inclusion_proof_v1_get_hashes(inclusion_proof_v1_t *inp, size_t idx); -/** As inclusion_proof_v1_get_hashes, but take and return a const - * pointer - */ - const struct hash_st * inclusion_proof_v1_getconst_hashes(const inclusion_proof_v1_t *inp, size_t idx); -/** Change the element at position 'idx' of the dynamic array field - * hashes of the inclusion_proof_v1_t in 'inp', so that it will hold - * the value 'elt'. Free the previous value, if any. - */ -int inclusion_proof_v1_set_hashes(inclusion_proof_v1_t *inp, size_t idx, struct hash_st * elt); -/** As inclusion_proof_v1_set_hashes, but does not free the previous - * value. - */ -int inclusion_proof_v1_set0_hashes(inclusion_proof_v1_t *inp, size_t idx, struct hash_st * elt); -/** Append a new element 'elt' to the dynamic array field hashes of - * the inclusion_proof_v1_t in 'inp'. - */ -int inclusion_proof_v1_add_hashes(inclusion_proof_v1_t *inp, struct hash_st * elt); -/** Return a pointer to the variable-length array field hashes of - * 'inp'. - */ -struct hash_st * * inclusion_proof_v1_getarray_hashes(inclusion_proof_v1_t *inp); -/** As inclusion_proof_v1_get_hashes, but take and return a const - * pointer - */ -const struct hash_st * const * inclusion_proof_v1_getconstarray_hashes(const inclusion_proof_v1_t *inp); -/** Change the length of the variable-length array field hashes of - * 'inp' to 'newlen'.Fill extra elements with NULL; free removed - * elements. Return 0 on success; return -1 and set the error code on - * 'inp' on failure. - */ -int inclusion_proof_v1_setlen_hashes(inclusion_proof_v1_t *inp, size_t newlen); -/** Return a newly allocated request_v1 with all elements set to zero. - */ -request_v1_t *request_v1_new(void); -/** Release all storage held by the request_v1 in 'victim'. (Do - * nothing if 'victim' is NULL.) - */ -void request_v1_free(request_v1_t *victim); -/** Try to parse a request_v1 from the buffer in 'input', using up to - * 'len_in' bytes from the input buffer. On success, return the number - * of bytes consumed and set *output to the newly allocated - * request_v1_t. On failure, return -2 if the input appears truncated, - * and -1 if the input is otherwise invalid. - */ -ssize_t request_v1_parse(request_v1_t **output, const uint8_t *input, const size_t len_in); -/** Return the number of bytes we expect to need to encode the - * request_v1 in 'obj'. On failure, return a negative value. Note that - * this value may be an overestimate, and can even be an underestimate - * for certain unencodeable objects. - */ -ssize_t request_v1_encoded_len(const request_v1_t *obj); -/** Try to encode the request_v1 from 'input' into the buffer at - * 'output', using up to 'avail' bytes of the output buffer. On - * success, return the number of bytes used. On failure, return -2 if - * the buffer was not long enough, and -1 if the input was invalid. - */ -ssize_t request_v1_encode(uint8_t *output, size_t avail, const request_v1_t *input); -/** Check whether the internal state of the request_v1 in 'obj' is - * consistent. Return NULL if it is, and a short message if it is not. - */ -const char *request_v1_check(const request_v1_t *obj); -/** Clear any errors that were set on the object 'obj' by its setter - * functions. Return true iff errors were cleared. - */ -int request_v1_clear_errors(request_v1_t *obj); -/** Return the value of the magic field of the request_v1_t in 'inp' - */ -uint64_t request_v1_get_magic(const request_v1_t *inp); -/** Set the value of the magic field of the request_v1_t in 'inp' to - * 'val'. Return 0 on success; return -1 and set the error code on - * 'inp' on failure. - */ -int request_v1_set_magic(request_v1_t *inp, uint64_t val); -/** Return the value of the format field of the request_v1_t in 'inp' - */ -uint64_t request_v1_get_format(const request_v1_t *inp); -/** Set the value of the format field of the request_v1_t in 'inp' to - * 'val'. Return 0 on success; return -1 and set the error code on - * 'inp' on failure. - */ -int request_v1_set_format(request_v1_t *inp, uint64_t val); -/** Return the value of the request_get_entries field of the - * request_v1_t in 'inp' - */ -struct req_get_entries_v1_st * request_v1_get_request_get_entries(request_v1_t *inp); -/** As request_v1_get_request_get_entries, but take and return a const - * pointer - */ -const struct req_get_entries_v1_st * request_v1_getconst_request_get_entries(const request_v1_t *inp); -/** Set the value of the request_get_entries field of the request_v1_t - * in 'inp' to 'val'. Free the old value if any. Steals the - * referenceto 'val'.Return 0 on success; return -1 and set the error - * code on 'inp' on failure. - */ -int request_v1_set_request_get_entries(request_v1_t *inp, struct req_get_entries_v1_st *val); -/** As request_v1_set_request_get_entries, but does not free the - * previous value. - */ -int request_v1_set0_request_get_entries(request_v1_t *inp, struct req_get_entries_v1_st *val); -/** Return the value of the request_get_proof_by_hash field of the - * request_v1_t in 'inp' - */ -struct req_get_proof_by_hash_v1_st * request_v1_get_request_get_proof_by_hash(request_v1_t *inp); -/** As request_v1_get_request_get_proof_by_hash, but take and return a - * const pointer - */ -const struct req_get_proof_by_hash_v1_st * request_v1_getconst_request_get_proof_by_hash(const request_v1_t *inp); -/** Set the value of the request_get_proof_by_hash field of the - * request_v1_t in 'inp' to 'val'. Free the old value if any. Steals - * the referenceto 'val'.Return 0 on success; return -1 and set the - * error code on 'inp' on failure. - */ -int request_v1_set_request_get_proof_by_hash(request_v1_t *inp, struct req_get_proof_by_hash_v1_st *val); -/** As request_v1_set_request_get_proof_by_hash, but does not free the - * previous value. - */ -int request_v1_set0_request_get_proof_by_hash(request_v1_t *inp, struct req_get_proof_by_hash_v1_st *val); -/** Return the value of the request_get_consistency_proof field of the - * request_v1_t in 'inp' - */ -struct req_get_consistency_proof_v1_st * request_v1_get_request_get_consistency_proof(request_v1_t *inp); -/** As request_v1_get_request_get_consistency_proof, but take and - * return a const pointer - */ -const struct req_get_consistency_proof_v1_st * request_v1_getconst_request_get_consistency_proof(const request_v1_t *inp); -/** Set the value of the request_get_consistency_proof field of the - * request_v1_t in 'inp' to 'val'. Free the old value if any. Steals - * the referenceto 'val'.Return 0 on success; return -1 and set the - * error code on 'inp' on failure. - */ -int request_v1_set_request_get_consistency_proof(request_v1_t *inp, struct req_get_consistency_proof_v1_st *val); -/** As request_v1_set_request_get_consistency_proof, but does not free - * the previous value. - */ -int request_v1_set0_request_get_consistency_proof(request_v1_t *inp, struct req_get_consistency_proof_v1_st *val); -/** Return a newly allocated signed_tree_head_v1 with all elements set - * to zero. - */ -signed_tree_head_v1_t *signed_tree_head_v1_new(void); -/** Release all storage held by the signed_tree_head_v1 in 'victim'. - * (Do nothing if 'victim' is NULL.) - */ -void signed_tree_head_v1_free(signed_tree_head_v1_t *victim); -/** Try to parse a signed_tree_head_v1 from the buffer in 'input', - * using up to 'len_in' bytes from the input buffer. On success, - * return the number of bytes consumed and set *output to the newly - * allocated signed_tree_head_v1_t. On failure, return -2 if the input - * appears truncated, and -1 if the input is otherwise invalid. - */ -ssize_t signed_tree_head_v1_parse(signed_tree_head_v1_t **output, const uint8_t *input, const size_t len_in); -/** Return the number of bytes we expect to need to encode the - * signed_tree_head_v1 in 'obj'. On failure, return a negative value. - * Note that this value may be an overestimate, and can even be an - * underestimate for certain unencodeable objects. - */ -ssize_t signed_tree_head_v1_encoded_len(const signed_tree_head_v1_t *obj); -/** Try to encode the signed_tree_head_v1 from 'input' into the buffer - * at 'output', using up to 'avail' bytes of the output buffer. On - * success, return the number of bytes used. On failure, return -2 if - * the buffer was not long enough, and -1 if the input was invalid. - */ -ssize_t signed_tree_head_v1_encode(uint8_t *output, size_t avail, const signed_tree_head_v1_t *input); -/** Check whether the internal state of the signed_tree_head_v1 in - * 'obj' is consistent. Return NULL if it is, and a short message if - * it is not. - */ -const char *signed_tree_head_v1_check(const signed_tree_head_v1_t *obj); -/** Clear any errors that were set on the object 'obj' by its setter - * functions. Return true iff errors were cleared. - */ -int signed_tree_head_v1_clear_errors(signed_tree_head_v1_t *obj); -/** Return the value of the magic field of the signed_tree_head_v1_t - * in 'inp' - */ -uint64_t signed_tree_head_v1_get_magic(const signed_tree_head_v1_t *inp); -/** Set the value of the magic field of the signed_tree_head_v1_t in - * 'inp' to 'val'. Return 0 on success; return -1 and set the error - * code on 'inp' on failure. - */ -int signed_tree_head_v1_set_magic(signed_tree_head_v1_t *inp, uint64_t val); -/** Return the value of the format field of the signed_tree_head_v1_t - * in 'inp' - */ -uint64_t signed_tree_head_v1_get_format(const signed_tree_head_v1_t *inp); -/** Set the value of the format field of the signed_tree_head_v1_t in - * 'inp' to 'val'. Return 0 on success; return -1 and set the error - * code on 'inp' on failure. - */ -int signed_tree_head_v1_set_format(signed_tree_head_v1_t *inp, uint64_t val); -/** Return the value of the timestamp field of the - * signed_tree_head_v1_t in 'inp' - */ -uint64_t signed_tree_head_v1_get_timestamp(const signed_tree_head_v1_t *inp); -/** Set the value of the timestamp field of the signed_tree_head_v1_t - * in 'inp' to 'val'. Return 0 on success; return -1 and set the error - * code on 'inp' on failure. - */ -int signed_tree_head_v1_set_timestamp(signed_tree_head_v1_t *inp, uint64_t val); -/** Return the value of the tree_size field of the - * signed_tree_head_v1_t in 'inp' - */ -uint64_t signed_tree_head_v1_get_tree_size(const signed_tree_head_v1_t *inp); -/** Set the value of the tree_size field of the signed_tree_head_v1_t - * in 'inp' to 'val'. Return 0 on success; return -1 and set the error - * code on 'inp' on failure. - */ -int signed_tree_head_v1_set_tree_size(signed_tree_head_v1_t *inp, uint64_t val); -/** Return the (constant) length of the array holding the root_hash - * field of the signed_tree_head_v1_t in 'inp'. - */ -size_t signed_tree_head_v1_getlen_root_hash(const signed_tree_head_v1_t *inp); -/** Return the element at position 'idx' of the fixed array field - * root_hash of the signed_tree_head_v1_t in 'inp'. - */ -uint8_t signed_tree_head_v1_get_root_hash(signed_tree_head_v1_t *inp, size_t idx); -/** As signed_tree_head_v1_get_root_hash, but take and return a const - * pointer - */ -uint8_t signed_tree_head_v1_getconst_root_hash(const signed_tree_head_v1_t *inp, size_t idx); -/** Change the element at position 'idx' of the fixed array field - * root_hash of the signed_tree_head_v1_t in 'inp', so that it will - * hold the value 'elt'. - */ -int signed_tree_head_v1_set_root_hash(signed_tree_head_v1_t *inp, size_t idx, uint8_t elt); -/** Return a pointer to the 32-element array field root_hash of 'inp'. - */ -uint8_t * signed_tree_head_v1_getarray_root_hash(signed_tree_head_v1_t *inp); -/** As signed_tree_head_v1_get_root_hash, but take and return a const - * pointer - */ -const uint8_t * signed_tree_head_v1_getconstarray_root_hash(const signed_tree_head_v1_t *inp); -/** Return the value of the n_items field of the signed_tree_head_v1_t - * in 'inp' - */ -uint64_t signed_tree_head_v1_get_n_items(const signed_tree_head_v1_t *inp); -/** Set the value of the n_items field of the signed_tree_head_v1_t in - * 'inp' to 'val'. Return 0 on success; return -1 and set the error - * code on 'inp' on failure. - */ -int signed_tree_head_v1_set_n_items(signed_tree_head_v1_t *inp, uint64_t val); -/** Return the length of the dynamic array holding the signatures - * field of the signed_tree_head_v1_t in 'inp'. - */ -size_t signed_tree_head_v1_getlen_signatures(const signed_tree_head_v1_t *inp); -/** Return the element at position 'idx' of the dynamic array field - * signatures of the signed_tree_head_v1_t in 'inp'. - */ -struct sigident_ed25519_st * signed_tree_head_v1_get_signatures(signed_tree_head_v1_t *inp, size_t idx); -/** As signed_tree_head_v1_get_signatures, but take and return a const - * pointer - */ - const struct sigident_ed25519_st * signed_tree_head_v1_getconst_signatures(const signed_tree_head_v1_t *inp, size_t idx); -/** Change the element at position 'idx' of the dynamic array field - * signatures of the signed_tree_head_v1_t in 'inp', so that it will - * hold the value 'elt'. Free the previous value, if any. - */ -int signed_tree_head_v1_set_signatures(signed_tree_head_v1_t *inp, size_t idx, struct sigident_ed25519_st * elt); -/** As signed_tree_head_v1_set_signatures, but does not free the - * previous value. - */ -int signed_tree_head_v1_set0_signatures(signed_tree_head_v1_t *inp, size_t idx, struct sigident_ed25519_st * elt); -/** Append a new element 'elt' to the dynamic array field signatures - * of the signed_tree_head_v1_t in 'inp'. - */ -int signed_tree_head_v1_add_signatures(signed_tree_head_v1_t *inp, struct sigident_ed25519_st * elt); -/** Return a pointer to the variable-length array field signatures of - * 'inp'. - */ -struct sigident_ed25519_st * * signed_tree_head_v1_getarray_signatures(signed_tree_head_v1_t *inp); -/** As signed_tree_head_v1_get_signatures, but take and return a const - * pointer - */ -const struct sigident_ed25519_st * const * signed_tree_head_v1_getconstarray_signatures(const signed_tree_head_v1_t *inp); -/** Change the length of the variable-length array field signatures of - * 'inp' to 'newlen'.Fill extra elements with NULL; free removed - * elements. Return 0 on success; return -1 and set the error code on - * 'inp' on failure. - */ -int signed_tree_head_v1_setlen_signatures(signed_tree_head_v1_t *inp, size_t newlen); - - -#endif diff --git a/trunnel/stfe.trunnel b/trunnel/stfe.trunnel deleted file mode 100644 index 8a26d92..0000000 --- a/trunnel/stfe.trunnel +++ /dev/null @@ -1,94 +0,0 @@ -/* always POST for consistency? */ - -const MAGIC_V1 = 0x535446455f5f5631; /* "STFE__V1" */ - -const T_GET_ENTRIES_V1 = 1; -const T_GET_PROOF_BY_HASH_V1 = 2; -const T_GET_CONSISTENCY_PROOF_V1 = 3; - -const T_ENTRIES_V1 = 4; -const T_INCLUSION_PROOF_V1 = 5; -const T_CONSISTENCY_PROOF_V1 = 6; -const T_SIGNED_TREE_HEAD_V1 = 7; - -struct req_get_entries_v1 { - u64 start_size; - u64 end_size; -}; - -struct req_get_proof_by_hash_v1 { - u64 tree_size; - u8 leaf_hash[32]; -}; - -struct req_get_consistency_proof_v1 { - u64 old_size; - u64 new_size; -}; - -struct request_v1 { - u64 magic IN [ MAGIC_V1 ]; - u64 format IN [ T_GET_ENTRIES_V1, T_GET_PROOF_BY_HASH_V1, T_GET_CONSISTENCY_PROOF_V1 ]; - - union request[format] { - T_GET_ENTRIES_V1: struct req_get_entries_v1 get_entries; - T_GET_PROOF_BY_HASH_V1: struct req_get_proof_by_hash_v1 get_proof_by_hash; - T_GET_CONSISTENCY_PROOF_V1: struct req_get_consistency_proof_v1 get_consistency_proof; - default: fail; - }; -} - -struct sigident_ed25519 { - u8 signature[64]; - u8 identifier[32]; -}; - -struct hash { - u8 hash[32]; -}; - -struct signed_checksum32_ed25519 { - u8 checksum[32]; - u64 length IN [ 1..128 ]; - u8 identifier[length]; - u8 signature[64]; - u8 namespace[32]; -}; - -struct entries_v1 { - u64 magic IN [ MAGIC_V1 ]; - u64 format IN [ T_ENTRIES_V1 ]; - u64 n_items; - struct signed_checksum32_ed25519 checksums[n_items]; -}; - -struct inclusion_proof_v1 { - u64 magic IN [ MAGIC_V1 ]; - u64 format IN [ T_INCLUSION_PROOF_V1 ]; - u8 identifier[32]; - u64 tree_size; - u64 leaf_index; - u64 n_items; - struct hash hashes[n_items]; -}; - -struct consistency_proof_v1 { - u64 magic IN [ MAGIC_V1 ]; - u64 format IN [ T_CONSISTENCY_PROOF_V1 ]; - u8 identifier[32]; - u64 old_size; - u64 new_size; - u64 n_items; - struct hash hashes[n_items]; -}; - -struct signed_tree_head_v1 { - u64 magic IN [ MAGIC_V1 ]; - u64 format IN [ T_SIGNED_TREE_HEAD_V1 ]; - u64 timestamp; - u64 tree_size; - u8 root_hash[32]; - u64 n_items; - struct sigident_ed25519 signatures[n_items]; -}; - -- cgit v1.2.3 From af5b14b9e9f85fe15253fbdb48945a302f0b7bec Mon Sep 17 00:00:00 2001 From: Linus Nordberg Date: Tue, 11 May 2021 14:05:31 +0200 Subject: signatures are 64 octets Spotted by Rasmus. --- doc/api.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/doc/api.md b/doc/api.md index c6a4569..4f43d2c 100644 --- a/doc/api.md +++ b/doc/api.md @@ -109,7 +109,7 @@ struct message { struct tree_leaf { struct message; - u8 signature_over_message[32]; + u8 signature_over_message[64]; u8 key_hash[32]; } ``` -- cgit v1.2.3 From 6ab06df1cd3dca8f4367ee009dde77a7b2fb79b1 Mon Sep 17 00:00:00 2001 From: Rasmus Dahlberg Date: Wed, 12 May 2021 16:24:05 +0200 Subject: added a first take on claimant model There might be a few inconsistencies and errors. To be discussed! --- doc/claimant.md | 84 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 84 insertions(+) create mode 100644 doc/claimant.md diff --git a/doc/claimant.md b/doc/claimant.md new file mode 100644 index 0000000..2aeebf0 --- /dev/null +++ b/doc/claimant.md @@ -0,0 +1,84 @@ +# Claimant model +## **SystemCHECKSUM**: +SystemCHECKSUM is about the claims made by a _data publisher_. +* **ClaimCHECKSUM**: + _I, data publisher, claim that the data_: + 1. has cryptographic hash X + 2. can be located using X as an identifier + 3. has properties Y (_"ecosystem specific_") +* **StatementCHECKSUM**: signed checksum
+* **ClaimantCHECKSUM**: data publisher
+ The data publisher is a party that wants to publish some data to an + end-user. +* **BelieverCHECKSUM**: end-user
+ Belief is based on seeing a valid StatementCHECKSUM. +* **VerifierCHECKSUM**: any interested party
+ These parties try to verify the above claims. For example: + * the data publisher itself (_"has my identity been compromised?"_) + * third-parties that want to look further into the data (_"ecosystem + specific_") +* **ArbiterCHECKSUM**:
+ There's no official body. Invalidated claims would affect reputation. + +**Example.** +The published data could be an executable binary from a reproducible build. The +ecosystem-specific claim would be that the corresponding source code can be +looked-up in a public database using X as an identifier. A rebuilder would +verify this claim by compiling the source, comparing the hashed output to the +claimed value. + +## **SystemCHECKSUM-LOG**: +SystemCHECKSUM-LOG is about the claims made by a _log operator_. +It adds _discoverability_ into SystemCHECKSUM. Discoverability means +that VerifierCHECKSUM can see all StatementCHECKSUM that +BelieverCHECKSUM will accept. + +* **ClaimCHECKSUM-LOG**: + _I, log operator, make available:_ + 1. a globally consistent append-only log of StatementCHECKSUM +* **StatementCHECKSUM-LOG**: signed tree head +* **ClaimantCHECKSUM-LOG**: log operator
+ Possible operators might be: + * a small subset of data publishers + * members of relevant consortia +* **BelieverCHECKSUM-LOG**: + BelieverCHECKSUM and + VerifierCHECKSUM
+ Belief is based on two factors: + 1. seeing a valid StatementCHECKSUM-LOG + 2. seeing a number of valid StatementCHECKSUM-WITNESS from + independent instances on SystemCHECKSUM-WITNESS + + A _policy_ defines the exact conditions that must be met. +* **VerifierCHECKSUM-LOG**: SystemCHECKSUM-WITNESS
+ Witnesses verify the log's append-only property from their own local + vantage point(s). +* **ArbiterCHECKSUM-LOG**:
+ There is no official body. The ecosystem at large should stop using an + instance of SystemCHECKSUM-LOG if cryptographic proofs of log + misbehavior are preseneted by some VerifierCHECKSUM-LOG. + +## **SystemCHECKSUM-WITNESS**: +SystemCHECKSUM-WITNESS is about making the claims of a log operator +_trustworthy_. +* **ClaimCHECKSUM-WITNESS**: + _I, witness, claim that_: + 1. SystemCHECKSUM-LOG provides a locally consistent append-only + log +* **StatementCHECKSUM-WITNESS**: signed tree head +* **ClaimantCHECKSUM-WITNESS**: third party
+ Examples of parties that may take on this role include: + * members of relevant consortia + * non-profits and other reputable organizations + * security enthusiasts and researchers + * log operators (cross-ecosystem) + * monitors (cross-ecosystem) + * a small subset of data publishers (cross-ecosystem) +* **BelieverCHECKSUM-WITNESS**: + BelieverCHECKSUM and + VerifierCHECKSUM
+ Belief is based on seeing a valid StatementCHECKSUM-WITNESS. +* **VerifierCHECKSUM-WITNESS**: n/a
+ Witnesses are trusted parties. Security is based on _strength in numbers_. +* **ArbiterCHECKSUM-WITNESS**:
+ There is no official body. Invalidated claims would affect reputation. -- cgit v1.2.3 From caf91fa52c192c188adb14a81219602628d46d9d Mon Sep 17 00:00:00 2001 From: Rasmus Dahlberg Date: Wed, 12 May 2021 16:32:18 +0200 Subject: fixed spacing typos --- doc/claimant.md | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/doc/claimant.md b/doc/claimant.md index 2aeebf0..c10e657 100644 --- a/doc/claimant.md +++ b/doc/claimant.md @@ -27,7 +27,7 @@ looked-up in a public database using X as an identifier. A rebuilder would verify this claim by compiling the source, comparing the hashed output to the claimed value. -## **SystemCHECKSUM-LOG**: +## **SystemCHECKSUM-LOG**: SystemCHECKSUM-LOG is about the claims made by a _log operator_. It adds _discoverability_ into SystemCHECKSUM. Discoverability means that VerifierCHECKSUM can see all StatementCHECKSUM that @@ -47,9 +47,7 @@ BelieverCHECKSUM will accept. Belief is based on two factors: 1. seeing a valid StatementCHECKSUM-LOG 2. seeing a number of valid StatementCHECKSUM-WITNESS from - independent instances on SystemCHECKSUM-WITNESS - - A _policy_ defines the exact conditions that must be met. + independent instances on SystemCHECKSUM-WITNESS. * **VerifierCHECKSUM-LOG**: SystemCHECKSUM-WITNESS
Witnesses verify the log's append-only property from their own local vantage point(s). @@ -58,7 +56,7 @@ BelieverCHECKSUM will accept. instance of SystemCHECKSUM-LOG if cryptographic proofs of log misbehavior are preseneted by some VerifierCHECKSUM-LOG. -## **SystemCHECKSUM-WITNESS**: +## **SystemCHECKSUM-WITNESS**: SystemCHECKSUM-WITNESS is about making the claims of a log operator _trustworthy_. * **ClaimCHECKSUM-WITNESS**: -- cgit v1.2.3 From 540306404d792ed7387ab0d8ca63632e7750aed3 Mon Sep 17 00:00:00 2001 From: Rasmus Dahlberg Date: Thu, 13 May 2021 12:33:09 +0200 Subject: added claimant model, take 2 There might be some inconsistencies and errors. To be discussed! --- doc/claimant.md | 57 ++++++++++++++++++++++++++++++++++----------------------- 1 file changed, 34 insertions(+), 23 deletions(-) diff --git a/doc/claimant.md b/doc/claimant.md index c10e657..b98f2ad 100644 --- a/doc/claimant.md +++ b/doc/claimant.md @@ -1,37 +1,48 @@ # Claimant model -## **SystemCHECKSUM**: -SystemCHECKSUM is about the claims made by a _data publisher_. +## **SystemCHECKSUM** +SystemCHECKSUM is about the claims made by a data publisher. * **ClaimCHECKSUM**: _I, data publisher, claim that the data_: 1. has cryptographic hash X - 2. can be located using X as an identifier - 3. has properties Y (_"ecosystem specific_") + 2. is produced by no-one but myself * **StatementCHECKSUM**: signed checksum
* **ClaimantCHECKSUM**: data publisher
The data publisher is a party that wants to publish some data to an end-user. * **BelieverCHECKSUM**: end-user
Belief is based on seeing a valid StatementCHECKSUM. -* **VerifierCHECKSUM**: any interested party
- These parties try to verify the above claims. For example: - * the data publisher itself (_"has my identity been compromised?"_) - * third-parties that want to look further into the data (_"ecosystem - specific_") +* **VerifierCHECKSUM**: data publisher
+ The data publisher tries to detect unwanted statements. * **ArbiterCHECKSUM**:
There's no official body. Invalidated claims would affect reputation. -**Example.** -The published data could be an executable binary from a reproducible build. The -ecosystem-specific claim would be that the corresponding source code can be -looked-up in a public database using X as an identifier. A rebuilder would -verify this claim by compiling the source, comparing the hashed output to the -claimed value. +SystemCHECKSUM\* can be defined to make more specific claims. Below +is a reproducible builds example. + +### **SystemCHECKSUM-RB**: +SystemCHECKSUM-RB is about the claims made by a _software publisher_ +that makes reproducible builds available. +* **ClaimCHECKSUM-RB**: + _I, software publisher, claim that the data_: + 1. has cryptographic hash X + 2. is the output of a reproducible build for which the source can be located + using X as an identifier +* **StatementCHECKSUM-RB**: StatementCHECKSUM +* **ClaimantCHECKSUM-RB**: software publisher
+* **BelieverCHECKSUM-RB**: end-user
+ Belief is based on seeing a valid StatementCHECKSUM-RB. +* **VerifierCHECKSUM-RB**: any interested party
+ These parties try to verify the above claims. For example: + * the software publisher itself (_"has my identity been compromised?"_) + * rebuilders that check for locatability and reproducibility +* **ArbiterCHECKSUM-RB**:
+ There's no official body. Invalidated claims would affect reputation. ## **SystemCHECKSUM-LOG**: SystemCHECKSUM-LOG is about the claims made by a _log operator_. -It adds _discoverability_ into SystemCHECKSUM. Discoverability means -that VerifierCHECKSUM can see all StatementCHECKSUM that -BelieverCHECKSUM will accept. +It adds _discoverability_ into SystemCHECKSUM\*. Discoverability +means that VerifierCHECKSUM\* can see all +StatementCHECKSUM that BelieverCHECKSUM\* accept. * **ClaimCHECKSUM-LOG**: _I, log operator, make available:_ @@ -42,12 +53,12 @@ BelieverCHECKSUM will accept. * a small subset of data publishers * members of relevant consortia * **BelieverCHECKSUM-LOG**: - BelieverCHECKSUM and - VerifierCHECKSUM
+ BelieverCHECKSUM\* and + VerifierCHECKSUM\*
Belief is based on two factors: 1. seeing a valid StatementCHECKSUM-LOG 2. seeing a number of valid StatementCHECKSUM-WITNESS from - independent instances on SystemCHECKSUM-WITNESS. + independent instances of SystemCHECKSUM-WITNESS. * **VerifierCHECKSUM-LOG**: SystemCHECKSUM-WITNESS
Witnesses verify the log's append-only property from their own local vantage point(s). @@ -73,8 +84,8 @@ _trustworthy_. * monitors (cross-ecosystem) * a small subset of data publishers (cross-ecosystem) * **BelieverCHECKSUM-WITNESS**: - BelieverCHECKSUM and - VerifierCHECKSUM
+ BelieverCHECKSUM\* and + VerifierCHECKSUM\*
Belief is based on seeing a valid StatementCHECKSUM-WITNESS. * **VerifierCHECKSUM-WITNESS**: n/a
Witnesses are trusted parties. Security is based on _strength in numbers_. -- cgit v1.2.3 From 5a780e8cd56509218123671be5826cbd2f0e8d2c Mon Sep 17 00:00:00 2001 From: Rasmus Dahlberg Date: Thu, 13 May 2021 16:00:37 +0200 Subject: added claimant model, take 3 --- doc/claimant.md | 52 +++++++++++++++------------------------------------- 1 file changed, 15 insertions(+), 37 deletions(-) diff --git a/doc/claimant.md b/doc/claimant.md index b98f2ad..6728fef 100644 --- a/doc/claimant.md +++ b/doc/claimant.md @@ -7,12 +7,11 @@ SystemCHECKSUM is about the claims made by a data publisher. 2. is produced by no-one but myself * **StatementCHECKSUM**: signed checksum
* **ClaimantCHECKSUM**: data publisher
- The data publisher is a party that wants to publish some data to an - end-user. + The data publisher is a party that wants to publish some data. * **BelieverCHECKSUM**: end-user
- Belief is based on seeing a valid StatementCHECKSUM. + The end-user is a party that wants to use some published data. * **VerifierCHECKSUM**: data publisher
- The data publisher tries to detect unwanted statements. + Only the data publisher can verify the above claims. * **ArbiterCHECKSUM**:
There's no official body. Invalidated claims would affect reputation. @@ -29,8 +28,11 @@ that makes reproducible builds available. using X as an identifier * **StatementCHECKSUM-RB**: StatementCHECKSUM * **ClaimantCHECKSUM-RB**: software publisher
+ The software publisher is a party that wants to publish the output of a + reproducible build. * **BelieverCHECKSUM-RB**: end-user
- Belief is based on seeing a valid StatementCHECKSUM-RB. + The end-user is a party that wants to run an executable binary that built + reproducibly. * **VerifierCHECKSUM-RB**: any interested party
These parties try to verify the above claims. For example: * the software publisher itself (_"has my identity been compromised?"_) @@ -53,41 +55,17 @@ StatementCHECKSUM that BelieverCHECKSUM\* accept. * a small subset of data publishers * members of relevant consortia * **BelieverCHECKSUM-LOG**: - BelieverCHECKSUM\* and - VerifierCHECKSUM\*
- Belief is based on two factors: - 1. seeing a valid StatementCHECKSUM-LOG - 2. seeing a number of valid StatementCHECKSUM-WITNESS from - independent instances of SystemCHECKSUM-WITNESS. -* **VerifierCHECKSUM-LOG**: SystemCHECKSUM-WITNESS
- Witnesses verify the log's append-only property from their own local - vantage point(s). -* **ArbiterCHECKSUM-LOG**:
- There is no official body. The ecosystem at large should stop using an - instance of SystemCHECKSUM-LOG if cryptographic proofs of log - misbehavior are preseneted by some VerifierCHECKSUM-LOG. - -## **SystemCHECKSUM-WITNESS**: -SystemCHECKSUM-WITNESS is about making the claims of a log operator -_trustworthy_. -* **ClaimCHECKSUM-WITNESS**: - _I, witness, claim that_: - 1. SystemCHECKSUM-LOG provides a locally consistent append-only - log -* **StatementCHECKSUM-WITNESS**: signed tree head -* **ClaimantCHECKSUM-WITNESS**: third party
- Examples of parties that may take on this role include: + * BelieverCHECKSUM\* + * VerifierCHECKSUM\*
+* **VerifierCHECKSUM-LOG**: third parties
+ These parties verify the above claims. Examples include: * members of relevant consortia * non-profits and other reputable organizations * security enthusiasts and researchers * log operators (cross-ecosystem) * monitors (cross-ecosystem) * a small subset of data publishers (cross-ecosystem) -* **BelieverCHECKSUM-WITNESS**: - BelieverCHECKSUM\* and - VerifierCHECKSUM\*
- Belief is based on seeing a valid StatementCHECKSUM-WITNESS. -* **VerifierCHECKSUM-WITNESS**: n/a
- Witnesses are trusted parties. Security is based on _strength in numbers_. -* **ArbiterCHECKSUM-WITNESS**:
- There is no official body. Invalidated claims would affect reputation. +* **ArbiterCHECKSUM-LOG**:
+ There is no official body. The ecosystem at large should stop using an + instance of SystemCHECKSUM-LOG if cryptographic proofs of log + misbehavior are preseneted by some VerifierCHECKSUM-LOG. -- cgit v1.2.3 From 2b2fc76121699e20c60dabb40e4507128731c0d5 Mon Sep 17 00:00:00 2001 From: Rasmus Dahlberg Date: Mon, 17 May 2021 22:04:33 +0200 Subject: started to refactor types and parsers Work in progress. --- types/cmd/new-namespace/main.go | 56 --- types/http.go | 167 +++++++++ types/http_test.go | 322 ++++++++++++++++++ types/namespace.go | 91 ----- types/namespace_pool.go | 69 ---- types/namespace_pool_test.go | 91 ----- types/namespace_test.go | 200 ----------- types/serialize.go | 50 --- types/serialize_test.go | 736 ---------------------------------------- types/stitem.go | 192 ----------- types/stitem_test.go | 64 ---- types/trunnel.go | 57 ++++ types/trunnel_test.go | 114 +++++++ types/types.go | 73 ++++ 14 files changed, 733 insertions(+), 1549 deletions(-) delete mode 100644 types/cmd/new-namespace/main.go create mode 100644 types/http.go create mode 100644 types/http_test.go delete mode 100644 types/namespace.go delete mode 100644 types/namespace_pool.go delete mode 100644 types/namespace_pool_test.go delete mode 100644 types/namespace_test.go delete mode 100644 types/serialize.go delete mode 100644 types/serialize_test.go delete mode 100644 types/stitem.go delete mode 100644 types/stitem_test.go create mode 100644 types/trunnel.go create mode 100644 types/trunnel_test.go create mode 100644 types/types.go diff --git a/types/cmd/new-namespace/main.go b/types/cmd/new-namespace/main.go deleted file mode 100644 index e338d7c..0000000 --- a/types/cmd/new-namespace/main.go +++ /dev/null @@ -1,56 +0,0 @@ -// Package main outputs the private and public parts of a new namespace -package main - -import ( - "flag" - "fmt" - - "crypto/ed25519" - "crypto/rand" - "encoding/base64" - - "github.com/golang/glog" - "github.com/system-transparency/stfe/types" -) - -var ( - format = flag.String("format", string(types.NamespaceFormatEd25519V1), "namespace format") -) - -func main() { - flag.Parse() - defer glog.Flush() - - switch *format { - case string(types.NamespaceFormatEd25519V1): - glog.Infof("generating new ed25519_v1 namespace") - sk, vk, namespace, err := genEd25519V1Namespace() - if err != nil { - glog.Errorf("genEd25519V1Namespace: %v", err) - break - } - fmt.Printf("sk: %s\n", base64.StdEncoding.EncodeToString(sk)) - fmt.Printf("vk: %s\n", base64.StdEncoding.EncodeToString(vk)) - fmt.Printf("ed25519_v1: %s\n", base64.StdEncoding.EncodeToString(namespace)) - default: - glog.Errorf("unsupported namespace format: %s", format) - } -} - -// genEd25519V1Namespace generates an Ed25519 secret key, verification key, and -// serialized ed25519_v1 namespace. -func genEd25519V1Namespace() ([]byte, []byte, []byte, error) { - vk, sk, err := ed25519.GenerateKey(rand.Reader) - if err != nil { - return nil, nil, nil, fmt.Errorf("ed25519.GenerateKey: %v", err) - } - namespace, err := types.NewNamespaceEd25519V1(vk[:]) - if err != nil { - return nil, nil, nil, fmt.Errorf("types.NewNamespaceEd25519V1: %v", err) - } - serialized, err := types.Marshal(*namespace) - if err != nil { - fmt.Errorf("types.Marshal: %v", err) - } - return sk, vk, serialized, nil -} diff --git a/types/http.go b/types/http.go new file mode 100644 index 0000000..cc61d26 --- /dev/null +++ b/types/http.go @@ -0,0 +1,167 @@ +package types + +import ( + "bytes" + "encoding/hex" + "fmt" + "net/http" + "strconv" + "strings" +) + +const ( + // HeaderPrefix is the start of every ST log HTTP header key + HeaderPrefix = "stlog-" + + // New leaf + HeaderShardHint = HeaderPrefix + "shard_hint" + HeaderChecksum = HeaderPrefix + "checksum" + HeaderSignatureOverMessage = HeaderPrefix + "signature_over_message" + HeaderVerificationKey = HeaderPrefix + "verification_key" + HeaderDomainHint = HeaderPrefix + "domain_hint" + + // Inclusion proof + HeaderLeafHash = HeaderPrefix + "leaf_hash" + HeaderLeafIndex = HeaderPrefix + "leaf_index" + HeaderInclusionPath = HeaderPrefix + "inclusion_path" + + // Consistency proof + HeaderNewSize = HeaderPrefix + "new_size" + HeaderOldSize = HeaderPrefix + "old_size" + HeaderConsistencyPath = HeaderPrefix + "consistency_path" + + // Range of leaves + HeaderStartSize = HeaderPrefix + "start_size" + HeaderEndSize = HeaderPrefix + "end_size" + + // Tree head + HeaderTimestamp = HeaderPrefix + "timestamp" + HeaderTreeSize = HeaderPrefix + "tree_size" + HeaderRootHash = HeaderPrefix + "root_hash" + + // Signature and signer identity + HeaderSignature = HeaderPrefix + "signature" + HeaderKeyHash = HeaderPrefix + "key_hash" +) + +// ToHTTP returns a signed tree-head as HTTP key-value pairs +func (sth *SignedTreeHead) ToHTTP() ([]byte, error) { + hdr := http.Header{} + hdr.Add(HeaderTimestamp, strconv.FormatUint(sth.Timestamp, 10)) + hdr.Add(HeaderTreeSize, strconv.FormatUint(sth.TreeSize, 10)) + hdr.Add(HeaderRootHash, hex.EncodeToString(sth.RootHash[:])) + for _, sigident := range sth.SigIdent { + hdr.Add(HeaderSignature, hex.EncodeToString(sigident.Signature[:])) + hdr.Add(HeaderKeyHash, hex.EncodeToString(sigident.KeyHash[:])) + } + + buf := bytes.NewBuffer(nil) + if err := hdr.Write(buf); err != nil { + return nil, fmt.Errorf("hdr.Write(): %v", err) // should not happen + } + return buf.Bytes(), nil +} + +// ToHTTP returns a consistency proof as HTTP key-value pairs +func (p *ConsistencyProof) ToHTTP() []byte { + return nil // TODO +} + +// ToHTTP returns an inclusion proof as HTTP key-value pairs +func (p *InclusionProof) ToHTTP() []byte { + return nil // TODO +} + +// ToHTTP returns a leaf as HTTP key-value pairs +func (l *Leaf) ToHTTP() []byte { + return nil // TODO +} + +// SignedTreeHeadFromHTTP parses a signed tree head from HTTP key-value pairs +func SignedTreeHeadFromHTTP(buf []byte) (*SignedTreeHead, error) { + hdr, err := headerFromBuf(buf) + if err != nil { + return nil, fmt.Errorf("headerFromBuf(): %v", err) + } + + // TreeHead + var sth SignedTreeHead + sth.Timestamp, err = strconv.ParseUint(hdr.Get(HeaderTimestamp), 10, 64) + if err != nil { + return nil, fmt.Errorf("invalid timestamp: %v", err) + } + sth.TreeSize, err = strconv.ParseUint(hdr.Get(HeaderTreeSize), 10, 64) + if err != nil { + return nil, fmt.Errorf("invalid tree size: %v", err) + } + if err := decodeHex(hdr.Get(HeaderRootHash), sth.RootHash[:]); err != nil { + return nil, fmt.Errorf("decodeHex(): %v", err) + } + + // SigIdent + signatures := hdr.Values(HeaderSignature) + keyHashes := hdr.Values(HeaderKeyHash) + if len(signatures) == 0 { + return nil, fmt.Errorf("no signer") + } + if len(signatures) != len(keyHashes) { + return nil, fmt.Errorf("mismatched signature-signer count") + } + for i := 0; i < len(signatures); i++ { + var sigident SigIdent + if err := decodeHex(signatures[i], sigident.Signature[:]); err != nil { + return nil, fmt.Errorf("decodeHex(): %v", err) + } + if err := decodeHex(keyHashes[i], sigident.KeyHash[:]); err != nil { + return nil, fmt.Errorf("decodeHex(): %v", err) + } + sth.SigIdent = append(sth.SigIdent, sigident) + } + return &sth, nil +} + +// ConsistencyProofFromHTTP parses a consistency proof from HTTP key-value pairs +func ConsistencyProofFromHTTP(buf []byte) (*ConsistencyProof, error) { + return nil, nil // TODO +} + +// InclusionProofFromHTTP parses an inclusion proof from HTTP key-value pairs +func InclusionProofFromHTTP(buf []byte) (*InclusionProof, error) { + return nil, nil // TODO +} + +// LeavesFromHTTP parses a list of leaves from HTTP key-value pairs +func LeavesFromHTTP(buf []byte) ([]*Leaf, error) { + return nil, nil // TODO +} + +// headerFromBuf parses ST log HTTP header key-value pairs from a response body +func headerFromBuf(buf []byte) (http.Header, error) { + hdr := http.Header{} + lines := strings.Split(string(buf), "\r\n") + lines = lines[:len(lines)-1] // skip the final empty line + for _, line := range lines { + split := strings.Split(line, ":") + if len(split) != 2 { + return nil, fmt.Errorf("invalid ST log HTTP header: %s", line) + } + if !strings.HasPrefix(strings.ToLower(split[0]), HeaderPrefix) { + return nil, fmt.Errorf("invalid ST log HTTP header prefix: %s", line) + } + hdr.Add(split[0], strings.TrimSpace(split[1])) + } + return hdr, nil +} + +// decodeHex decodes a hex-encoded string into a fixed-size output slice +func decodeHex(str string, out []byte) error { + buf, err := hex.DecodeString(str) + if err != nil { + return fmt.Errorf("hex.DecodeString(): %v", err) + } + if len(buf) != len(out) { + return fmt.Errorf("invalid length: %v", len(buf)) + } + copy(out, buf) + return nil +} diff --git a/types/http_test.go b/types/http_test.go new file mode 100644 index 0000000..8165edf --- /dev/null +++ b/types/http_test.go @@ -0,0 +1,322 @@ +package types + +import ( + "bytes" + "encoding/hex" + "net/http" + "reflect" + "strings" + "testing" +) + +var ( + testZeroBuffer32 = [32]byte{} + testZeroBuffer64 = [64]byte{} +) + +func TestSignedTreeHeadToHTTP(t *testing.T) { + description := "valid: cosigned tree head with two signatures" + sth := &SignedTreeHead{ + TreeHead: TreeHead{ + Timestamp: 0, + TreeSize: 0, + RootHash: testBuffer32, + }, + SigIdent: []SigIdent{ + SigIdent{ + Signature: testZeroBuffer64, + KeyHash: testZeroBuffer32, + }, + SigIdent{ + Signature: testBuffer64, + KeyHash: testBuffer32, + }, + }, + } + want := map[string][]string{ + HeaderTimestamp: []string{"0"}, + HeaderTreeSize: []string{"0"}, + HeaderRootHash: []string{hex.EncodeToString(testBuffer32[:])}, + HeaderSignature: []string{ + hex.EncodeToString(testZeroBuffer64[:]), + hex.EncodeToString(testBuffer64[:]), + }, + HeaderKeyHash: []string{ + hex.EncodeToString(testZeroBuffer32[:]), + hex.EncodeToString(testBuffer32[:]), + }, + } + buf, err := sth.ToHTTP() + if err != nil { + t.Fatalf("sth.ToHTTP: %v", err) + } + hdr, err := headerFromBuf(buf) + if err != nil { + t.Fatalf("headerFromBuf: %v", err) + } + compareHeaderWithMap(t, description, hdr, want) +} + +func TestSignedTreeHeadFromHTTP(t *testing.T) { + for _, table := range []struct { + description string + buf []byte + wantErr bool + wantSth *SignedTreeHead + }{ + { + description: "invalid: not ST log HTTP header", + buf: newHeaderBuf(t, map[string][]string{ + "user-agent": []string{"secret"}, + }), + wantErr: true, + }, + { + description: "invalid: timestamp", + buf: newHeaderBuf(t, map[string][]string{ + HeaderTreeSize: []string{"0"}, + HeaderRootHash: []string{hex.EncodeToString(testBuffer32[:])}, + HeaderSignature: []string{hex.EncodeToString(testBuffer64[:])}, + HeaderKeyHash: []string{hex.EncodeToString(testBuffer32[:])}, + }), + wantErr: true, + }, + { + description: "invalid: tree size", + buf: newHeaderBuf(t, map[string][]string{ + HeaderTimestamp: []string{"0"}, + HeaderRootHash: []string{hex.EncodeToString(testBuffer32[:])}, + HeaderSignature: []string{hex.EncodeToString(testBuffer64[:])}, + HeaderKeyHash: []string{hex.EncodeToString(testBuffer32[:])}, + }), + wantErr: true, + }, + { + description: "invalid: root hash", + buf: newHeaderBuf(t, map[string][]string{ + HeaderTimestamp: []string{"0"}, + HeaderTreeSize: []string{"0"}, + HeaderSignature: []string{hex.EncodeToString(testBuffer64[:])}, + HeaderKeyHash: []string{hex.EncodeToString(testBuffer32[:])}, + }), + wantErr: true, + }, + { + description: "invalid: signature", + buf: newHeaderBuf(t, map[string][]string{ + HeaderTimestamp: []string{"0"}, + HeaderTreeSize: []string{"0"}, + HeaderRootHash: []string{hex.EncodeToString(testBuffer32[:])}, + HeaderSignature: []string{hex.EncodeToString(testBuffer32[:])}, + HeaderKeyHash: []string{hex.EncodeToString(testBuffer32[:])}, + }), + wantErr: true, + }, + { + description: "invalid: key hash", + buf: newHeaderBuf(t, map[string][]string{ + HeaderTimestamp: []string{"0"}, + HeaderTreeSize: []string{"0"}, + HeaderRootHash: []string{hex.EncodeToString(testBuffer32[:])}, + HeaderSignature: []string{hex.EncodeToString(testBuffer64[:])}, + HeaderKeyHash: []string{hex.EncodeToString(testBuffer64[:])}, + }), + wantErr: true, + }, + { + description: "invalid: sigident count", + buf: newHeaderBuf(t, map[string][]string{ + HeaderTimestamp: []string{"0"}, + HeaderTreeSize: []string{"0"}, + HeaderRootHash: []string{hex.EncodeToString(testBuffer32[:])}, + HeaderSignature: []string{hex.EncodeToString(testBuffer64[:])}, + HeaderKeyHash: []string{ + hex.EncodeToString(testZeroBuffer32[:]), + hex.EncodeToString(testBuffer32[:]), + }, + }), + wantErr: true, + }, + { + description: "invalid: no signer", + buf: newHeaderBuf(t, map[string][]string{ + HeaderTimestamp: []string{"0"}, + HeaderTreeSize: []string{"0"}, + HeaderRootHash: []string{hex.EncodeToString(testBuffer32[:])}, + }), + wantErr: true, + }, + { + description: "valid: cosigned tree head with two signatures", + buf: newHeaderBuf(t, map[string][]string{ + HeaderTimestamp: []string{"0"}, + HeaderTreeSize: []string{"0"}, + HeaderRootHash: []string{hex.EncodeToString(testBuffer32[:])}, + HeaderSignature: []string{ + hex.EncodeToString(testZeroBuffer64[:]), + hex.EncodeToString(testBuffer64[:]), + }, + HeaderKeyHash: []string{ + hex.EncodeToString(testZeroBuffer32[:]), + hex.EncodeToString(testBuffer32[:]), + }, + }), + wantSth: &SignedTreeHead{ + TreeHead: TreeHead{ + Timestamp: 0, + TreeSize: 0, + RootHash: testBuffer32, + }, + SigIdent: []SigIdent{ + SigIdent{ + Signature: testZeroBuffer64, + KeyHash: testZeroBuffer32, + }, + SigIdent{ + Signature: testBuffer64, + KeyHash: testBuffer32, + }, + }, + }, + }, + } { + sth, err := SignedTreeHeadFromHTTP(table.buf) + if got, want := err != nil, table.wantErr; got != want { + t.Errorf("got error %v but wanted %v in test %q: %v", got, want, table.description, err) + } + if err != nil { + continue // nothing more to check on error + } + if got, want := sth, table.wantSth; !reflect.DeepEqual(got, want) { + t.Errorf("got signed tree head\n\t%v\nbut wanted\n\t%v\nin test %q", got, want, table.description) + } + } +} + +func TestHeaderFromBuf(t *testing.T) { + for _, table := range []struct { + description string + buf []byte + wantErr bool + wantMap map[string][]string + }{ + { + description: "invalid: split", + buf: []byte(HeaderPrefix + "k1: v1:v2\r\n"), + wantErr: true, + }, + { + description: "invalid: prefix", + buf: []byte("user-agent: secret\r\n"), + wantErr: true, + }, + { + description: "valid: one key with funky case", + buf: []byte(funkyCase(t, HeaderPrefix) + "k1: v1\r\n"), + wantMap: map[string][]string{ + HeaderPrefix + "k1": []string{"v1"}, + }, + }, + { + description: "valid: two keys where one has multiple values", + buf: []byte( + HeaderPrefix + "k1: v1 \r\n" + + HeaderPrefix + "k2: v2\r\n" + + HeaderPrefix + "k2: v3\r\n", + ), + wantMap: map[string][]string{ + HeaderPrefix + "k1": []string{"v1"}, + HeaderPrefix + "k2": []string{"v2", "v3"}, + }, + }, + } { + hdr, err := headerFromBuf(table.buf) + if got, want := err != nil, table.wantErr; got != want { + t.Errorf("got error %v but wanted %v in test %q: %v", got, want, table.description, err) + } + if err != nil { + continue // nothing more to check on error + } + compareHeaderWithMap(t, table.description, hdr, table.wantMap) + } +} + +func TestDecodeHex(t *testing.T) { + for _, table := range []struct { + description string + hex string + wantErr bool + wantBuf [4]byte + }{ + { + description: "invalid: too short input", + hex: "000102", + wantErr: true, + }, + { + description: "invalid: too large input", + hex: "0001020304", + wantErr: true, + }, + { + description: "invalid: not hex (1/2)", + hex: "000102030", + wantErr: true, + }, + { + description: "invalid: not hex (2/2)", + hex: "0001020q", + wantErr: true, + }, + { + description: "valid", + hex: "00010203", + wantBuf: [4]byte{0, 1, 2, 3}, + }, + } { + var buf [4]byte + err := decodeHex(table.hex, buf[:]) + if got, want := err != nil, table.wantErr; got != want { + t.Errorf("got error %v but wanted %v in test %q: %v", got, want, table.description, err) + } + if err != nil { + continue // nothing more to check on error + } + if got, want := buf[:], table.wantBuf[:]; !bytes.Equal(got, want) { + t.Errorf("got buf %v but wanted %v in test %q", got, want, table.description) + } + } +} + +func newHeaderBuf(t *testing.T, kv map[string][]string) []byte { + t.Helper() + hdr := http.Header{} + for key, values := range kv { + for _, value := range values { + hdr.Add(key, value) + } + } + buf := bytes.NewBuffer(nil) + if err := hdr.Write(buf); err != nil { + t.Fatalf("hdr.Write(): %v", err) + } + return buf.Bytes() +} + +func compareHeaderWithMap(t *testing.T, description string, hdr http.Header, wantMap map[string][]string) { + t.Helper() + if got, want := len(hdr), len(wantMap); got != want { + t.Errorf("got %d keys but wanted %d in test %q", got, want, description) + } + for key, value := range wantMap { + if got, want := hdr.Values(key), value; !reflect.DeepEqual(got, want) { + t.Errorf("got value %v but wanted %v for key %v in test %q", got, want, key, description) + } + } +} + +func funkyCase(t *testing.T, str string) string { + t.Helper() + splitIndex := len(str) / 2 + return strings.ToLower(str[:splitIndex]) + strings.ToUpper(str[splitIndex:]) +} diff --git a/types/namespace.go b/types/namespace.go deleted file mode 100644 index 376ebcd..0000000 --- a/types/namespace.go +++ /dev/null @@ -1,91 +0,0 @@ -package types - -import ( - "fmt" - - "crypto/ed25519" - - "github.com/google/certificate-transparency-go/tls" -) - -// NamespaceFormat defines a particular namespace type that is versioend -type NamespaceFormat tls.Enum - -const ( - NamespaceFormatReserved NamespaceFormat = 0 - NamespaceFormatEd25519V1 NamespaceFormat = 1 - - NamespaceFingerprintSize = 32 -) - -// Namespace references a versioned namespace based on a given format specifier -type Namespace struct { - Format NamespaceFormat `tls:"maxval:65535"` - Ed25519V1 *Ed25519V1 `tls:"selector:Format,val:1"` -} - -// Ed25519V1 uses an Ed25519 verification key as namespace. Encoding, -// signing, and verification operations are defined by RFC 8032. -type Ed25519V1 struct { - Namespace [32]byte -} - -func (f NamespaceFormat) String() string { - switch f { - case NamespaceFormatReserved: - return "reserved" - case NamespaceFormatEd25519V1: - return "ed25519_v1" - default: - return fmt.Sprintf("unknown NamespaceFormat: %d", f) - } -} - -func (n Namespace) String() string { - switch n.Format { - case NamespaceFormatReserved: - return fmt.Sprintf("Format(%s)", n.Format) - case NamespaceFormatEd25519V1: - return fmt.Sprintf("Format(%s): %+v", n.Format, n.Ed25519V1) - default: - return fmt.Sprintf("unknown Namespace: %v", n.Format) - } -} - -// Fingerprint returns a fixed-size namespace fingerprint that is unique. -func (n *Namespace) Fingerprint() (*[NamespaceFingerprintSize]byte, error) { - switch n.Format { - case NamespaceFormatEd25519V1: - return &n.Ed25519V1.Namespace, nil - default: - return nil, fmt.Errorf("unsupported NamespaceFormat: %v", n.Format) - } -} - -// Verify checks that signature is valid over message for this namespace -func (ns *Namespace) Verify(message, signature []byte) error { - switch ns.Format { - case NamespaceFormatEd25519V1: - if !ed25519.Verify(ed25519.PublicKey(ns.Ed25519V1.Namespace[:]), message, signature) { - return fmt.Errorf("ed25519 signature verification failed") - } - default: - return fmt.Errorf("namespace not supported: %v", ns.Format) - } - return nil -} - -// NewNamespaceEd25519V1 returns an new Ed25519V1 namespace based on a -// verification key. -func NewNamespaceEd25519V1(vk []byte) (*Namespace, error) { - if len(vk) != 32 { - return nil, fmt.Errorf("invalid verification key: must be 32 bytes") - } - - var ed25519v1 Ed25519V1 - copy(ed25519v1.Namespace[:], vk) - return &Namespace{ - Format: NamespaceFormatEd25519V1, - Ed25519V1: &ed25519v1, - }, nil -} diff --git a/types/namespace_pool.go b/types/namespace_pool.go deleted file mode 100644 index 1e9e8f6..0000000 --- a/types/namespace_pool.go +++ /dev/null @@ -1,69 +0,0 @@ -package types - -import ( - "fmt" -) - -// NamespacePool is a pool of namespaces that contain complete verification keys -type NamespacePool struct { - pool map[[NamespaceFingerprintSize]byte]*Namespace - list []*Namespace - // If we need to update this structure without a restart => add mutex. -} - -// NewNameSpacePool creates a new namespace pool from a list of namespaces. An -// error is returned if there are duplicate namespaces or namespaces without a -// complete verification key. The latter is determined by namespaceWithKey(). -func NewNamespacePool(namespaces []*Namespace) (*NamespacePool, error) { - np := &NamespacePool{ - pool: make(map[[NamespaceFingerprintSize]byte]*Namespace), - list: make([]*Namespace, 0), - } - for _, namespace := range namespaces { - if !namespaceWithKey(namespace.Format) { - return nil, fmt.Errorf("need verification key in namespace pool: %v", namespace.Format) - } - fpr, err := namespace.Fingerprint() - if err != nil { - return nil, fmt.Errorf("need fingerprint in namespace pool: %v", err) - } - if _, ok := np.pool[*fpr]; ok { - return nil, fmt.Errorf("duplicate namespace: %v", namespace.String()) - } - np.pool[*fpr] = namespace - np.list = append(np.list, namespace) - } - return np, nil -} - -// Find checks if namespace is a member of the namespace pool. -func (np *NamespacePool) Find(namespace *Namespace) (*Namespace, bool) { - fpr, err := namespace.Fingerprint() - if err != nil { - return nil, false - } - if _, ok := np.pool[*fpr]; !ok { - return nil, false - } - // If the passed namespace is a key fingerprint the actual key needs to be - // attached before returning. Not applicable for Ed25519. Docdoc later. - return namespace, true -} - -// List returns a copied list of namespaces that is used by this pool. -func (np *NamespacePool) List() []*Namespace { - namespaces := make([]*Namespace, len(np.list)) - copy(namespaces, np.list) - return namespaces -} - -// namespaceWithKey returns true if a namespace format contains a complete -// verification key. I.e., some formats might have a key fingerprint instead. -func namespaceWithKey(format NamespaceFormat) bool { - switch format { - case NamespaceFormatEd25519V1: - return true - default: - return false - } -} diff --git a/types/namespace_pool_test.go b/types/namespace_pool_test.go deleted file mode 100644 index f5810a2..0000000 --- a/types/namespace_pool_test.go +++ /dev/null @@ -1,91 +0,0 @@ -package types - -import ( - "bytes" - "reflect" - "testing" -) - -func TestNewNamespacePool(t *testing.T) { - ns1 := mustInitNamespaceEd25519V1(t, 0x00) - ns2 := mustInitNamespaceEd25519V1(t, 0xff) - nsr := &Namespace{Format: NamespaceFormatReserved} - for _, table := range []struct { - description string - namespaces []*Namespace - wantErr bool - }{ - { - description: "invalid: duplicate namespace", - namespaces: []*Namespace{ns1, ns1, ns2}, - wantErr: true, - }, - { - description: "invalid: namespace without key", - namespaces: []*Namespace{ns1, nsr, ns2}, - wantErr: true, - }, - { - description: "valid: empty", - namespaces: []*Namespace{}, - }, - { - description: "valid: one namespace", - namespaces: []*Namespace{ns1}, - }, - { - description: "valid: two namespaces", - namespaces: []*Namespace{ns1, ns2}, - }, - } { - _, err := NewNamespacePool(table.namespaces) - if got, want := err != nil, table.wantErr; got != want { - t.Errorf("got error %v but wanted %v in test %q: %v", got, want, table.description, err) - } - } -} - -func TestFind(t *testing.T) { - ns1 := mustInitNamespaceEd25519V1(t, 0x00) - ns2 := mustInitNamespaceEd25519V1(t, 0xff) - - // Empty pool - pool, err := NewNamespacePool(nil) - if err != nil { - t.Fatalf("must create new namespace pool: %v", err) - } - if _, ok := pool.Find(ns1); ok { - t.Errorf("found namespace in empty pool") - } - - // Pool with one namespace - pool, err = NewNamespacePool([]*Namespace{ns1}) - if err != nil { - t.Fatalf("must create new namespace pool: %v", err) - } - if ns, ok := pool.Find(ns1); !ok { - t.Errorf("could not find namespace that is a member of the pool") - } else if !reflect.DeepEqual(ns, ns1) { - t.Errorf("found namespace but it is wrong") - } - if _, ok := pool.Find(ns2); ok { - t.Errorf("found namespace although it is not a member of the pool") - } -} - -func TestList(t *testing.T) { - ns1 := mustInitNamespaceEd25519V1(t, 0x00) - ns2 := mustInitNamespaceEd25519V1(t, 0xff) - namespaces := []*Namespace{ns1, ns2} - pool, err := NewNamespacePool(namespaces) - if err != nil { - t.Fatalf("must create new namespace pool: %v", err) - } - if got, want := len(pool.List()), len(namespaces); got != want { - t.Errorf("got len %v but wanted %v", got, want) - } - pool.List()[0] = ns2 - if got, want := pool.List()[0].Ed25519V1.Namespace[:], ns1.Ed25519V1.Namespace[:]; !bytes.Equal(got, want) { - t.Errorf("returned list is not a copy") - } -} diff --git a/types/namespace_test.go b/types/namespace_test.go deleted file mode 100644 index a5847ef..0000000 --- a/types/namespace_test.go +++ /dev/null @@ -1,200 +0,0 @@ -package types - -import ( - "bytes" - "strings" - "testing" - - "crypto/ed25519" -) - -// TestNamespaceString checks that the String() function prints the right -// format, and that the body is printed without a nil-pointer panic. -func TestNamespaceString(t *testing.T) { - wantPrefix := map[NamespaceFormat]string{ - NamespaceFormatReserved: "Format(reserved)", - NamespaceFormatEd25519V1: "Format(ed25519_v1): &{Namespace", - NamespaceFormat(1<<16 - 1): "unknown Namespace: unknown NamespaceFormat: 65535", - } - tests := append(test_cases_namespace(t), testCaseSerialize{ - description: "valid: unknown Namespace", - item: Namespace{ - Format: NamespaceFormat(1<<16 - 1), - }, - }) - for _, table := range tests { - namespace, ok := table.item.(Namespace) - if !ok { - t.Fatalf("must cast to Namespace in test %q", table.description) - } - - prefix, ok := wantPrefix[namespace.Format] - if !ok { - t.Fatalf("must have prefix for StFormat %v in test %q", namespace.Format, table.description) - } - if got, want := namespace.String(), prefix; !strings.HasPrefix(got, want) { - t.Errorf("got %q but wanted prefix %q in test %q", got, want, table.description) - } - } -} - -func TestFingerprint(t *testing.T) { - for _, table := range []struct { - description string - namespace *Namespace - wantErr bool - wantFpr [NamespaceFingerprintSize]byte - }{ - { - description: "invalid: no fingerprint for type", - namespace: &Namespace{ - Format: NamespaceFormatReserved, - }, - wantErr: true, - }, - { - description: "valid: ed25519_v1", - namespace: mustInitNamespaceEd25519V1(t, 0xaf), - wantFpr: func() (ret [NamespaceFingerprintSize]byte) { - for i, _ := range ret { - ret[i] = 0xaf - } - return - }(), - }, - } { - fpr, err := table.namespace.Fingerprint() - if got, want := err != nil, table.wantErr; got != want { - t.Errorf("got error %v but wanted %v in test %q: %v", got, want, table.description, err) - } - if err != nil { - continue - } - if got, want := *fpr, table.wantFpr; !bytes.Equal(got[:], want[:]) { - t.Errorf("got fpr %v but wanted %v in test %q", got, want, table.description) - } - } -} - -func TestVerify(t *testing.T) { - var tests []testCaseNamespace - tests = append(tests, test_cases_verify(t)...) - tests = append(tests, test_cases_verify_ed25519v1(t)...) - for _, table := range tests { - err := table.namespace.Verify(table.msg, table.sig) - if got, want := err != nil, table.wantErr; got != want { - t.Errorf("got error=%v but wanted %v in test %q: %v", got, want, table.description, err) - } - } -} - -func TestNewNamespaceEd25519V1(t *testing.T) { - size := 32 // verification key size - for _, table := range []struct { - description string - vk []byte - wantErr bool - }{ - { - description: "invalid", - vk: make([]byte, size+1), - wantErr: true, - }, - { - description: "valid", - vk: make([]byte, size), - }, - } { - n, err := NewNamespaceEd25519V1(table.vk) - if got, want := err != nil, table.wantErr; got != want { - t.Errorf("got error %v but wanted %v in test %q: %v", got, want, table.description, err) - } - if err != nil { - continue - } - if got, want := n.Format, NamespaceFormatEd25519V1; got != want { - t.Errorf("got namespace format %v but wanted %v in test %q", got, want, table.description) - continue - } - if got, want := n.Ed25519V1.Namespace[:], table.vk; !bytes.Equal(got, want) { - t.Errorf("got namespace %X but wanted %X in test %q", got, want, table.description) - } - } -} - -// testCaseNamespace is a common test case used for Namespace.Verify() tests -type testCaseNamespace struct { - description string - namespace *Namespace - msg, sig []byte - wantErr bool -} - -// test_cases_verify returns basic namespace.Verify() tests -func test_cases_verify(t *testing.T) []testCaseNamespace { - return []testCaseNamespace{ - { - description: "test_cases_verify: invalid: unsupported namespace", - namespace: &Namespace{Format: NamespaceFormatReserved}, - msg: []byte("msg"), - sig: []byte("sig"), - wantErr: true, - }, - } -} - -// test_cases_verify_ed25519v1 returns ed25519_v1 Namespace.Verify() tests -func test_cases_verify_ed25519v1(t *testing.T) []testCaseNamespace { - testEd25519Sk := [64]byte{230, 122, 195, 152, 194, 195, 147, 153, 80, 120, 153, 79, 102, 27, 52, 187, 136, 218, 150, 234, 107, 9, 167, 4, 92, 21, 11, 113, 42, 29, 129, 69, 75, 60, 249, 150, 229, 93, 75, 32, 103, 126, 244, 37, 53, 182, 68, 82, 249, 109, 49, 94, 10, 19, 146, 244, 58, 191, 169, 107, 78, 37, 45, 210} - testEd25519Vk := [32]byte{75, 60, 249, 150, 229, 93, 75, 32, 103, 126, 244, 37, 53, 182, 68, 82, 249, 109, 49, 94, 10, 19, 146, 244, 58, 191, 169, 107, 78, 37, 45, 210} - return []testCaseNamespace{ - { - description: "test_cases_verify_ed25519v1: invalid: sk signed message, but vk is not for sk", - namespace: &Namespace{ - Format: NamespaceFormatEd25519V1, - Ed25519V1: &Ed25519V1{ - Namespace: [32]byte{}, - }, - }, - msg: []byte("message"), - sig: ed25519.Sign(ed25519.PrivateKey(testEd25519Sk[:]), []byte("message")), - wantErr: true, - }, - { - description: "test_cases_verify_ed25519v1: invalid: vk is for sk, but sk did not sign message", - namespace: &Namespace{ - Format: NamespaceFormatEd25519V1, - Ed25519V1: &Ed25519V1{ - Namespace: testEd25519Vk, - }, - }, - msg: []byte("some message"), - sig: ed25519.Sign(ed25519.PrivateKey(testEd25519Sk[:]), []byte("another message")), - wantErr: true, - }, - { - description: "test_cases_verify_ed25519v1: valid", - namespace: &Namespace{ - Format: NamespaceFormatEd25519V1, - Ed25519V1: &Ed25519V1{ - Namespace: testEd25519Vk, - }, - }, - msg: []byte("message"), - sig: ed25519.Sign(ed25519.PrivateKey(testEd25519Sk[:]), []byte("message")), - }, - } -} - -func mustInitNamespaceEd25519V1(t *testing.T, initByte byte) *Namespace { - t.Helper() - buf := make([]byte, 32) - for i := 0; i < len(buf); i++ { - buf[i] = initByte - } - ns, err := NewNamespaceEd25519V1(buf) - if err != nil { - t.Fatalf("must make Ed25519v1 namespace: %v", err) - } - return ns -} diff --git a/types/serialize.go b/types/serialize.go deleted file mode 100644 index fd93336..0000000 --- a/types/serialize.go +++ /dev/null @@ -1,50 +0,0 @@ -package types - -import ( - "fmt" - - "github.com/google/certificate-transparency-go/tls" -) - -const ( - HashSizeV1 = 32 -) - -// GetProofByHashV1 is a serializable get-proof-by-hash request -type GetProofByHashV1 struct { - Hash [HashSizeV1]byte - TreeSize uint64 -} - -// GetConsistencyProofV1 is a serializable get-consistency-proof request -type GetConsistencyProofV1 struct { - First uint64 - Second uint64 -} - -// GetEntriesV1 is a serializable get-entries request -type GetEntriesV1 struct { - Start uint64 - End uint64 -} - -// Marshal marshals a TLS-encodable structure -func Marshal(item interface{}) ([]byte, error) { - serialized, err := tls.Marshal(item) - if err != nil { - return nil, fmt.Errorf("tls.Marshal: %v", err) - } - return serialized, nil -} - -// Unmarshal unmarshals a TLS-encoded structure -func Unmarshal(serialized []byte, out interface{}) error { - extra, err := tls.Unmarshal(serialized, out) - if err != nil { - return fmt.Errorf("tls.Unmarshal: %v", err) - } - if len(extra) > 0 { - return fmt.Errorf("tls.Unmarshal: extra data: %X", extra) - } - return nil -} diff --git a/types/serialize_test.go b/types/serialize_test.go deleted file mode 100644 index a06effe..0000000 --- a/types/serialize_test.go +++ /dev/null @@ -1,736 +0,0 @@ -package types - -import ( - "bytes" - "testing" - - "encoding/binary" -) - -// testCaseSerialize is a common test case used for ST log types -type testCaseSerialize struct { - description string - item interface{} - wantErr bool - wantBytes []byte // only used if no error and not equal to nil -} - -// TestMarshalUnmarshal tests that valid ST log structures can be marshalled and -// then unmarshalled without error, and that invalid ST log structures cannot be -// marshalled. If wantBytes is non-nil the marshalled result must also match. -func TestMarshalUnmarshal(t *testing.T) { - var tests []testCaseSerialize - tests = append(tests, test_cases_stitemlist(t)...) - tests = append(tests, test_cases_stitem(t)...) - tests = append(tests, test_cases_sthv1(t)...) - tests = append(tests, test_cases_costhv1(t)...) - tests = append(tests, test_cases_cpv1(t)...) - tests = append(tests, test_cases_ipv1(t)...) - tests = append(tests, test_cases_signed_checksumv1(t)...) - tests = append(tests, test_cases_checksumv1(t)...) - tests = append(tests, test_cases_thv1(t)...) - tests = append(tests, test_cases_nh(t)...) - tests = append(tests, test_cases_sigv1(t)...) - tests = append(tests, test_cases_namespace(t)...) - tests = append(tests, test_cases_ed25519v1(t)...) - tests = append(tests, test_cases_requests(t)...) - for _, table := range tests { - b, err := Marshal(table.item) - if got, want := err != nil, table.wantErr; got != want { - t.Errorf("got error %v but wanted %v in test %q: %v", got, want, table.description, err) - } - if err != nil { - continue // nothing to unmarshal - } - if got, want := b, table.wantBytes; want != nil && !bytes.Equal(got, want) { - t.Errorf("got bytes \n%v\n\tbut wanted\n%v\n\t in test %q: %v", got, want, table.description, err) - } - - switch table.item.(type) { - case StItemList: - var item StItemList - err = Unmarshal(b, &item) - case StItem: - var item StItem - err = Unmarshal(b, &item) - case SignedTreeHeadV1: - var item SignedTreeHeadV1 - err = Unmarshal(b, &item) - case CosignedTreeHeadV1: - var item CosignedTreeHeadV1 - err = Unmarshal(b, &item) - case ConsistencyProofV1: - var item ConsistencyProofV1 - err = Unmarshal(b, &item) - case InclusionProofV1: - var item InclusionProofV1 - err = Unmarshal(b, &item) - case SignedChecksumV1: - var item SignedChecksumV1 - err = Unmarshal(b, &item) - case ChecksumV1: - var item ChecksumV1 - err = Unmarshal(b, &item) - case TreeHeadV1: - var item TreeHeadV1 - err = Unmarshal(b, &item) - case NodeHash: - var item NodeHash - err = Unmarshal(b, &item) - case SignatureV1: - var item SignatureV1 - err = Unmarshal(b, &item) - case Namespace: - var item Namespace - err = Unmarshal(b, &item) - case Ed25519V1: - var item Ed25519V1 - err = Unmarshal(b, &item) - case GetProofByHashV1: - var item GetProofByHashV1 - err = Unmarshal(b, &item) - case GetConsistencyProofV1: - var item GetConsistencyProofV1 - err = Unmarshal(b, &item) - case GetEntriesV1: - var item GetEntriesV1 - err = Unmarshal(b, &item) - default: - t.Errorf("unhandled type in test %q", table.description) - } - if err != nil { - t.Errorf("unmarshal failed but wanted success in test %q: %v", table.description, err) - } - } -} - -// TestUnmarshalStItem tests that invalid StItems cannot be unmarshalled -func TestUnmarshalStItem(t *testing.T) { - tests := test_cases_stitem(t)[1:] // skip reserved type - for _, table := range tests { - description := table.description[7:] // skip "valid: " prefix - b, err := Marshal(table.item) - if err != nil { - t.Fatalf("must marshal in test %q: %v", description, err) - } - - var item StItem - if err := Unmarshal(append(b[:], []byte{0}...), &item); err == nil { - t.Errorf("unmarshal suceeded with one extra byte in test %q", description) - } - if err := Unmarshal(b[:len(b)-1], &item); err == nil { - t.Errorf("unmarshal suceeded with one byte short in test %q", description) - } - if err := Unmarshal(append(b[:], b[:]...), &item); err == nil { - t.Errorf("unmarshal succeeded with appended StItem in test %q", description) - } - if err := Unmarshal([]byte{0}, &item); err == nil { - t.Errorf("unmarshal succeeded with a single byte in test %q", description) - } - } -} - -// test_cases_stitemlist returns test cases for the StItemList type -func test_cases_stitemlist(t *testing.T) []testCaseSerialize { - t.Helper() - return []testCaseSerialize{ - testCaseSerialize{ - description: "test_cases_stitemlist: valid: StItemList: empty", - item: StItemList{}, - wantBytes: []byte{0x00, 0x00, 0x00, 0x00}, - }, // skip max len check because it is huge - testCaseSerialize{ - description: "test_cases_stitemlist: valid: mixed content", - item: testStItemList, - wantBytes: testStItemListBytes, - }, // other invalid bounds are already tested in subtypes - } -} - -// test_cases_stitem returns test cases for the different StItem types -func test_cases_stitem(t *testing.T) []testCaseSerialize { - t.Helper() - return []testCaseSerialize{ - { - description: "invalid: StItem: reserved", - item: testStItemReserved, - wantErr: true, - }, - { - description: "valid: StItem: signed_tree_head_v1", - item: testStItemSignedTreeHeadV1, - wantBytes: testStItemSignedTreeHeadV1Bytes, - }, - { - description: "valid: StItem: cosigned_tree_head_v1", - item: testStItemCosignedTreeHeadV1, - wantBytes: testStItemCosignedTreeHeadV1Bytes, - }, - { - description: "valid: StItem: consistency_proof_v1", - item: testStItemConsistencyProofV1, - wantBytes: testStItemConsistencyProofV1Bytes, - }, - { - description: "valid: StItem: inclusion_proof_v1", - item: testStItemInclusionProofV1, - wantBytes: testStItemInclusionProofV1Bytes, - }, - { - description: "valid: StItem: signed_checksum_v1", - item: testStItemSignedChecksumV1, - wantBytes: testStItemSignedChecksumV1Bytes, - }, // other invalid bounds are already tested in subtypes - } -} - -// test_cases_sthv1 returns test cases for the SignedTreeHeadV1 structure -func test_cases_sthv1(t *testing.T) []testCaseSerialize { - t.Helper() - return []testCaseSerialize{ - { - description: "valid: testSignedTreeHeadV1", - item: testSignedTreeHeadV1, - wantBytes: testSignedTreeHeadV1Bytes, - }, // other invalid bounds are already tested in subtypes - } -} - -// test_cases_costhv1 returns test cases for the CosignedTreeHeadV1 structure -func test_cases_costhv1(t *testing.T) []testCaseSerialize { - t.Helper() - return []testCaseSerialize{ - { - description: "test_cases_costhv1: valid: min", - item: CosignedTreeHeadV1{ - SignedTreeHead: testSignedTreeHeadV1, - Cosignatures: make([]SignatureV1, 0), - }, - }, // skipping "valid: max" because it is huge - { - description: "test_cases_costhv1: testCosignedTreeHeadV1", - item: testCosignedTreeHeadV1, - wantBytes: testCosignedTreeHeadV1Bytes, - }, // other invalid bounds are already tested in subtypes - } -} - -// test_cases_cpv1 returns test cases for the ConsistencyProofV1 structure -func test_cases_cpv1(t *testing.T) []testCaseSerialize { - t.Helper() - max := 65535 // max consistency proof - return []testCaseSerialize{ - { - description: "test_cases_cpv1: invalid: >max", - item: ConsistencyProofV1{ - LogId: testNamespace, - TreeSize1: 0, - TreeSize2: 0, - ConsistencyPath: func() []NodeHash { - var path []NodeHash - for sum := 0; sum < max+1; sum += 1 + len(testNodeHash.Data) { - path = append(path, testNodeHash) - } - return path - }(), - }, - wantErr: true, - }, - { - description: "test_cases_cpv1: valid: min", - item: ConsistencyProofV1{ - LogId: testNamespace, - TreeSize1: 0, - TreeSize2: 0, - ConsistencyPath: make([]NodeHash, 0), - }, - }, - { - description: "test_cases_cpv1: valid: testConsistencyProofV1", - item: testConsistencyProofV1, - wantBytes: testConsistencyProofV1Bytes, - }, // other invalid bounds are already tested in subtypes - } -} - -// test_cases_ipv1 returns test cases for the InclusionProofV1 structure -func test_cases_ipv1(t *testing.T) []testCaseSerialize { - t.Helper() - max := 65535 // max inclusion proof - return []testCaseSerialize{ - { - description: "test_cases_ipv1: invalid: >max", - item: InclusionProofV1{ - LogId: testNamespace, - TreeSize: 0, - LeafIndex: 0, - InclusionPath: func() []NodeHash { - var path []NodeHash - for sum := 0; sum < max+1; sum += 1 + len(testNodeHash.Data) { - path = append(path, testNodeHash) - } - return path - }(), - }, - wantErr: true, - }, - { - description: "test_cases_ipv1: valid: min", - item: InclusionProofV1{ - LogId: testNamespace, - TreeSize: 0, - LeafIndex: 0, - InclusionPath: make([]NodeHash, 0), - }, - }, - { - description: "test_cases_ipv1: valid: testInclusionProofV1", - item: testInclusionProofV1, - wantBytes: testInclusionProofV1Bytes, - }, // other invalid bounds are already tested in subtypes - } -} - -// test_cases_signed_checksumv1 returns test cases for the SignedChecksumV1 structure -func test_cases_signed_checksumv1(t *testing.T) []testCaseSerialize { - t.Helper() - return []testCaseSerialize{ - { - description: "test_cases_signed_checksumv1: valid: testSignedChecksumV1", - item: testSignedChecksumV1, - wantBytes: testSignedChecksumV1Bytes, - }, // other invalid bounds are already tested in subtypes - } -} - -// test_cases_checksumv1 returns test cases for the ChecksumV1 structure -func test_cases_checksumv1(t *testing.T) []testCaseSerialize { - t.Helper() - minIdentifier, maxIdentifier, identifier := 1, 128, []byte("foobar-1-2-3") - minChecksum, maxChecksum, checksum := 1, 64, make([]byte, 32) - return []testCaseSerialize{ - { - description: "test_cases_checksumv1: invalid: identifier: min", - item: ChecksumV1{ - Identifier: make([]byte, minIdentifier-1), - Checksum: checksum, - }, - wantErr: true, - }, - { - description: "test_cases_checksumv1: invalid: identifier: max", - item: ChecksumV1{ - Identifier: make([]byte, maxIdentifier+1), - Checksum: checksum, - }, - wantErr: true, - }, - { - description: "test_cases_checksumv1: invalid: checksum: min", - item: ChecksumV1{ - Identifier: identifier, - Checksum: make([]byte, minChecksum-1), - }, - wantErr: true, - }, - { - description: "test_cases_checksumv1: invalid: checksum: max", - item: ChecksumV1{ - Identifier: identifier, - Checksum: make([]byte, maxChecksum+1), - }, - wantErr: true, - }, - { - description: "test_cases_checksumv1: valid: testChecksumV1", - item: testChecksumV1, - wantBytes: testChecksumV1Bytes, - }, - } -} - -// test_cases_thv1 returns test cases for the TreeHeadV1 structure -func test_cases_thv1(t *testing.T) []testCaseSerialize { - t.Helper() - min, max := 0, 1<<16-1 // extensions min and max - return []testCaseSerialize{ - { - description: "test_cases_thv1: invalid: max", - item: TreeHeadV1{ - Timestamp: 0, - TreeSize: 0, - RootHash: testNodeHash, - Extension: make([]byte, max+1), - }, - wantErr: true, - }, - { - description: "test_cases_thv1: valid: min", - item: TreeHeadV1{ - Timestamp: 0, - TreeSize: 0, - RootHash: testNodeHash, - Extension: make([]byte, min), - }, - }, - { - description: "test_cases_thv1: valid: max", - item: TreeHeadV1{ - Timestamp: 0, - TreeSize: 0, - RootHash: testNodeHash, - Extension: make([]byte, max), - }, - }, - { - description: "test_cases_thv1: valid: testTreeHeadV1", - item: testTreeHeadV1, - wantBytes: testTreeHeadV1Bytes, - }, // other invalid bounds are already tested in subtypes - } -} - -// test_cases_nh returns test cases for the NodeHash structure -func test_cases_nh(t *testing.T) []testCaseSerialize { - t.Helper() - min, max := 32, 1<<8-1 // NodeHash min and max - return []testCaseSerialize{ - { - description: "test_cases_nh: invalid: min", - item: NodeHash{make([]byte, min-1)}, - wantErr: true, - }, - { - description: "test_cases_nh: invalid: max", - item: NodeHash{make([]byte, max+1)}, - wantErr: true, - }, - { - description: "test_cases_nh: valid: min", - item: NodeHash{make([]byte, min)}, - }, - { - description: "test_cases_nh: valid: max", - item: NodeHash{make([]byte, max)}, - }, - { - description: "test_cases_nh: valid: testNodeHash", - item: testNodeHash, - wantBytes: testNodeHashBytes, - }, // other invalid bounds are already tested in subtypes - } -} - -// test_cases_sigv1 returns test cases for the SignatureV1 structure -func test_cases_sigv1(t *testing.T) []testCaseSerialize { - t.Helper() - min, max := 1, 1<<16-1 // signature min and max - return []testCaseSerialize{ - { - description: "test_cases_sigv1: invalid: min", - item: SignatureV1{ - Namespace: testNamespace, - Signature: make([]byte, min-1), - }, - wantErr: true, - }, - { - description: "test_cases_sigv1: invalid: max", - item: SignatureV1{ - Namespace: testNamespace, - Signature: make([]byte, max+1), - }, - wantErr: true, - }, - { - description: "test_cases_sigv1: valid: min", - item: SignatureV1{ - Namespace: testNamespace, - Signature: make([]byte, min), - }, - }, - { - description: "test_cases_sigv1: valid: max", - item: SignatureV1{ - Namespace: testNamespace, - Signature: make([]byte, max), - }, - }, - { - description: "test_cases_sigV1: valid: testSignatureV1", - item: testSignatureV1, - wantBytes: testSignatureV1Bytes, - }, - } -} - -// test_cases_namespace returns test cases for the different Namespace types. -func test_cases_namespace(t *testing.T) []testCaseSerialize { - return []testCaseSerialize{ - { - description: "invalid: Namespace: reserved", - item: testNamespaceReserved, - wantErr: true, - }, - { - description: "valid: Namespace: ed25519_v1", - item: testNamespaceEd25519V1, - wantBytes: testNamespaceEd25519V1Bytes, - }, - } -} - -// test_cases_ed25519v1 returns test cases for the Ed25519V1 structure -func test_cases_ed25519v1(t *testing.T) []testCaseSerialize { - return []testCaseSerialize{ - { - description: "valid: testNamespaceEd25519V1", - item: testEd25519V1, - wantBytes: testEd25519V1Bytes, - }, - } -} - -// test_cases_requests returns test cases for proof request types -func test_cases_requests(t *testing.T) []testCaseSerialize { - return []testCaseSerialize{ - { - description: "valid: GetProofByHashV1", - item: GetProofByHashV1{ - Hash: [HashSizeV1]byte{}, - TreeSize: 16909060, - }, - wantBytes: bytes.Join([][]byte{ - make([]byte, 32), // hash - []byte{0x00, 0x00, 0x00, 0x00, 0x1, 0x2, 0x3, 0x4}, // tree size - }, nil), - }, - { - description: "valid: GetConsistencyProofV1", - item: GetConsistencyProofV1{ - First: 0, - Second: 16909060, - }, - wantBytes: bytes.Join([][]byte{ - make([]byte, 8), // first - []byte{0x00, 0x00, 0x00, 0x00, 0x1, 0x2, 0x3, 0x4}, // second - }, nil), - }, - { - description: "valid: GetEntriesV1", - item: GetEntriesV1{ - Start: 0, - End: 16909060, - }, - wantBytes: bytes.Join([][]byte{ - make([]byte, 8), // start - []byte{0x00, 0x00, 0x00, 0x00, 0x1, 0x2, 0x3, 0x4}, // end - }, nil), - }, - } -} - -var ( - // StItemList - testStItemList = StItemList{ - Items: []StItem{ - testStItemSignedChecksumV1, - testStItemInclusionProofV1, - testStItemCosignedTreeHeadV1, - }, - } - testStItemListBytes = bytes.Join([][]byte{ - func() []byte { - sum := uint32(len(testStItemSignedChecksumV1Bytes)) - sum += uint32(len(testStItemInclusionProofV1Bytes)) - sum += uint32(len(testStItemCosignedTreeHeadV1Bytes)) - buf := make([]byte, 4) - binary.BigEndian.PutUint32(buf, sum) - return buf - }(), // length specifier list - testStItemSignedChecksumV1Bytes, // first StItem - testStItemInclusionProofV1Bytes, // second StItem - testStItemCosignedTreeHeadV1Bytes, // third StItem - }, nil) - - // StItem - testStItemReserved = StItem{ - Format: StFormatReserved, - } - - testStItemSignedTreeHeadV1 = StItem{ - Format: StFormatSignedTreeHeadV1, - SignedTreeHeadV1: &testSignedTreeHeadV1, - } - testStItemSignedTreeHeadV1Bytes = bytes.Join([][]byte{ - []byte{0x00, 0x01}, // format signed_tree_head_v1 - testSignedTreeHeadV1Bytes, // SignedTreeHeadV1 - }, nil) - - testStItemCosignedTreeHeadV1 = StItem{ - Format: StFormatCosignedTreeHeadV1, - CosignedTreeHeadV1: &testCosignedTreeHeadV1, - } - testStItemCosignedTreeHeadV1Bytes = bytes.Join([][]byte{ - []byte{0x00, 0x02}, // format cosigned_tree_head_v1 - testCosignedTreeHeadV1Bytes, // CosignedTreeHeadV1, - }, nil) - - testStItemConsistencyProofV1 = StItem{ - Format: StFormatConsistencyProofV1, - ConsistencyProofV1: &testConsistencyProofV1, - } - testStItemConsistencyProofV1Bytes = bytes.Join([][]byte{ - []byte{0x00, 0x03}, // format consistency_proof_v1 - testConsistencyProofV1Bytes, // ConsistencyProofV1 - }, nil) - - testStItemInclusionProofV1 = StItem{ - Format: StFormatInclusionProofV1, - InclusionProofV1: &testInclusionProofV1, - } - testStItemInclusionProofV1Bytes = bytes.Join([][]byte{ - []byte{0x00, 0x04}, // format inclusion_proof_v1 - testInclusionProofV1Bytes, // InclusionProofV1 - }, nil) - - testStItemSignedChecksumV1 = StItem{ - Format: StFormatSignedChecksumV1, - SignedChecksumV1: &testSignedChecksumV1, - } - testStItemSignedChecksumV1Bytes = bytes.Join([][]byte{ - []byte{0x00, 0x05}, // format signed_checksum_v1 - testSignedChecksumV1Bytes, // SignedChecksumV1 - }, nil) - - // Subtypes used by StItem - testSignedTreeHeadV1 = SignedTreeHeadV1{ - TreeHead: testTreeHeadV1, - Signature: testSignatureV1, - } - testSignedTreeHeadV1Bytes = bytes.Join([][]byte{ - testTreeHeadV1Bytes, // tree head - testSignatureV1Bytes, // signature - }, nil) - - testCosignedTreeHeadV1 = CosignedTreeHeadV1{ - SignedTreeHead: testSignedTreeHeadV1, - Cosignatures: []SignatureV1{ - testSignatureV1, - }, - } - testCosignedTreeHeadV1Bytes = bytes.Join([][]byte{ - testSignedTreeHeadV1Bytes, // signed tree head - []byte{0x00, 0x00, 0x00, byte(len(testSignatureV1Bytes))}, // cosignature length specifier - testSignatureV1Bytes, // the only cosignature in this list - }, nil) - - testConsistencyProofV1 = ConsistencyProofV1{ - LogId: testNamespace, - TreeSize1: 16909060, - TreeSize2: 16909060, - ConsistencyPath: []NodeHash{ - testNodeHash, - }, - } - testConsistencyProofV1Bytes = bytes.Join([][]byte{ - testNamespaceBytes, // log id - []byte{0x00, 0x00, 0x00, 0x00, 0x01, 0x02, 0x03, 0x04}, // tree size 1 - []byte{0x00, 0x00, 0x00, 0x00, 0x01, 0x02, 0x03, 0x04}, // tree size 2 - []byte{0x00, byte(len(testNodeHashBytes))}, // consistency path length specifier - testNodeHashBytes, // the only node hash in this proof - }, nil) - - testInclusionProofV1 = InclusionProofV1{ - LogId: testNamespace, - TreeSize: 16909060, - LeafIndex: 16909060, - InclusionPath: []NodeHash{ - testNodeHash, - }, - } - testInclusionProofV1Bytes = bytes.Join([][]byte{ - testNamespaceBytes, // log id - []byte{0x00, 0x00, 0x00, 0x00, 0x01, 0x02, 0x03, 0x04}, // tree size - []byte{0x00, 0x00, 0x00, 0x00, 0x01, 0x02, 0x03, 0x04}, // leaf index - []byte{0x00, byte(len(testNodeHashBytes))}, // inclusion path length specifier - testNodeHashBytes, // the only node hash in this proof - }, nil) - - testSignedChecksumV1 = SignedChecksumV1{ - Data: testChecksumV1, - Signature: testSignatureV1, - } - testSignedChecksumV1Bytes = bytes.Join([][]byte{ - testChecksumV1Bytes, // data - testSignatureV1Bytes, // signature - }, nil) - - // Additional subtypes - testChecksumV1 = ChecksumV1{ - Identifier: []byte("foobar-1-2-3"), - Checksum: make([]byte, 32), - } - testChecksumV1Bytes = bytes.Join([][]byte{ - []byte{12}, // identifier length specifier - []byte("foobar-1-2-3"), // identifier - []byte{32}, // checksum length specifier - make([]byte, 32), // checksum - }, nil) - - testTreeHeadV1 = TreeHeadV1{ - Timestamp: 16909060, - TreeSize: 16909060, - RootHash: testNodeHash, - Extension: make([]byte, 0), - } - testTreeHeadV1Bytes = bytes.Join([][]byte{ - []byte{0x00, 0x00, 0x00, 0x00, 0x01, 0x02, 0x03, 0x04}, // timestamp - []byte{0x00, 0x00, 0x00, 0x00, 0x01, 0x02, 0x03, 0x04}, // tree size - testNodeHashBytes, // root hash - []byte{0x00, 0x00}, // extension length specifier - // no extension - }, nil) - - testNodeHash = NodeHash{ - Data: make([]byte, 32), - } - testNodeHashBytes = bytes.Join([][]byte{ - []byte{32}, // node hash length specifier - make([]byte, 32), - }, nil) - - testSignatureV1 = SignatureV1{ - Namespace: testNamespace, - Signature: make([]byte, 64), - } - testSignatureV1Bytes = bytes.Join([][]byte{ - testNamespaceBytes, // namespace field - []byte{0, 64}, // signature length specifier - make([]byte, 64), // signature - }, nil) - - // Namespace - testNamespaceReserved = Namespace{ - Format: NamespaceFormatReserved, - } - - testNamespace = testNamespaceEd25519V1 - testNamespaceBytes = testNamespaceEd25519V1Bytes - testNamespaceEd25519V1 = Namespace{ - Format: NamespaceFormatEd25519V1, - Ed25519V1: &testEd25519V1, - } - testNamespaceEd25519V1Bytes = bytes.Join([][]byte{ - []byte{0x00, 0x01}, // format ed25519_v1 - testEd25519V1Bytes, // Ed25519V1 - }, nil) - - // Subtypes used by Namespace - testEd25519V1 = Ed25519V1{ - Namespace: [32]byte{}, - } - testEd25519V1Bytes = bytes.Join([][]byte{ - make([]byte, 32), // namespace, no length specifier because fixed size - }, nil) -) diff --git a/types/stitem.go b/types/stitem.go deleted file mode 100644 index 447cad0..0000000 --- a/types/stitem.go +++ /dev/null @@ -1,192 +0,0 @@ -package types - -import ( - "fmt" - - "github.com/google/certificate-transparency-go/tls" -) - -// StFormat defines a particular StItem type that is versioned -type StFormat tls.Enum - -const ( - StFormatReserved StFormat = 0 - StFormatSignedTreeHeadV1 StFormat = 1 - StFormatCosignedTreeHeadV1 StFormat = 2 - StFormatConsistencyProofV1 StFormat = 3 - StFormatInclusionProofV1 StFormat = 4 - StFormatSignedChecksumV1 StFormat = 5 -) - -// StItem references a versioned item based on a given format specifier -type StItem struct { - Format StFormat `tls:"maxval:65535"` - SignedTreeHeadV1 *SignedTreeHeadV1 `tls:"selector:Format,val:1"` - CosignedTreeHeadV1 *CosignedTreeHeadV1 `tls:"selector:Format,val:2"` - ConsistencyProofV1 *ConsistencyProofV1 `tls:"selector:Format,val:3"` - InclusionProofV1 *InclusionProofV1 `tls:"selector:Format,val:4"` - SignedChecksumV1 *SignedChecksumV1 `tls:"selector:Format,val:5"` -} - -type StItemList struct { - Items []StItem `tls:"minlen:0,maxlen:4294967295"` -} - -type SignedTreeHeadV1 struct { - TreeHead TreeHeadV1 - Signature SignatureV1 -} - -type CosignedTreeHeadV1 struct { - SignedTreeHead SignedTreeHeadV1 - Cosignatures []SignatureV1 `tls:"minlen:0,maxlen:4294967295"` -} - -type ConsistencyProofV1 struct { - LogId Namespace - TreeSize1 uint64 - TreeSize2 uint64 - ConsistencyPath []NodeHash `tls:"minlen:0,maxlen:65535"` -} - -type InclusionProofV1 struct { - LogId Namespace - TreeSize uint64 - LeafIndex uint64 - InclusionPath []NodeHash `tls:"minlen:0,maxlen:65535"` -} - -type SignedChecksumV1 struct { - Data ChecksumV1 - Signature SignatureV1 -} - -type ChecksumV1 struct { - Identifier []byte `tls:"minlen:1,maxlen:128"` - Checksum []byte `tls:"minlen:1,maxlen:64"` -} - -type TreeHeadV1 struct { - Timestamp uint64 - TreeSize uint64 - RootHash NodeHash - Extension []byte `tls:"minlen:0,maxlen:65535"` -} - -type NodeHash struct { - Data []byte `tls:"minlen:32,maxlen:255"` -} - -type SignatureV1 struct { - Namespace Namespace - Signature []byte `tls:"minlen:1,maxlen:65535"` -} - -func (f StFormat) String() string { - switch f { - case StFormatReserved: - return "reserved" - case StFormatSignedTreeHeadV1: - return "signed_tree_head_v1" - case StFormatCosignedTreeHeadV1: - return "cosigned_tree_head_v1" - case StFormatConsistencyProofV1: - return "consistency_proof_v1" - case StFormatInclusionProofV1: - return "inclusion_proof_v1" - case StFormatSignedChecksumV1: - return "signed_checksum_v1" - default: - return fmt.Sprintf("unknown StFormat: %d", f) - } -} - -func (i StItem) String() string { - switch i.Format { - case StFormatReserved: - return fmt.Sprintf("Format(%s)", i.Format) - case StFormatSignedTreeHeadV1: - return fmt.Sprintf("Format(%s): %+v", i.Format, i.SignedTreeHeadV1) - case StFormatCosignedTreeHeadV1: - return fmt.Sprintf("Format(%s): %+v", i.Format, i.CosignedTreeHeadV1) - case StFormatConsistencyProofV1: - return fmt.Sprintf("Format(%s): %+v", i.Format, i.ConsistencyProofV1) - case StFormatInclusionProofV1: - return fmt.Sprintf("Format(%s): %+v", i.Format, i.InclusionProofV1) - case StFormatSignedChecksumV1: - return fmt.Sprintf("Format(%s): %+v", i.Format, i.SignedChecksumV1) - default: - return fmt.Sprintf("unknown StItem: %v", i.Format) - } -} - -func NewSignedTreeHeadV1(th *TreeHeadV1, sig *SignatureV1) *StItem { - return &StItem{ - Format: StFormatSignedTreeHeadV1, - SignedTreeHeadV1: &SignedTreeHeadV1{ - TreeHead: *th, - Signature: *sig, - }, - } -} - -func NewCosignedTreeHeadV1(sth *SignedTreeHeadV1, cosig []SignatureV1) *StItem { - if cosig == nil { - cosig = make([]SignatureV1, 0) - } - return &StItem{ - Format: StFormatCosignedTreeHeadV1, - CosignedTreeHeadV1: &CosignedTreeHeadV1{ - SignedTreeHead: *sth, - Cosignatures: cosig, - }, - } -} - -func NewConsistencyProofV1(id *Namespace, size1, size2 uint64, path []NodeHash) *StItem { - return &StItem{ - Format: StFormatConsistencyProofV1, - ConsistencyProofV1: &ConsistencyProofV1{ - LogId: *id, - TreeSize1: size1, - TreeSize2: size2, - ConsistencyPath: path, - }, - } -} - -func NewInclusionProofV1(id *Namespace, size, index uint64, path []NodeHash) *StItem { - return &StItem{ - Format: StFormatInclusionProofV1, - InclusionProofV1: &InclusionProofV1{ - LogId: *id, - TreeSize: size, - LeafIndex: index, - InclusionPath: path, - }, - } -} - -func NewSignedChecksumV1(data *ChecksumV1, sig *SignatureV1) *StItem { - return &StItem{ - Format: StFormatSignedChecksumV1, - SignedChecksumV1: &SignedChecksumV1{ - Data: *data, - Signature: *sig, - }, - } -} - -func NewTreeHeadV1(timestamp, size uint64, hash, extension []byte) *TreeHeadV1 { - if extension == nil { - extension = make([]byte, 0) - } - return &TreeHeadV1{ - Timestamp: timestamp, - TreeSize: size, - RootHash: NodeHash{ - Data: hash, - }, - Extension: extension, - } -} diff --git a/types/stitem_test.go b/types/stitem_test.go deleted file mode 100644 index 90d6808..0000000 --- a/types/stitem_test.go +++ /dev/null @@ -1,64 +0,0 @@ -package types - -import ( - "strings" - "testing" -) - -// TestStItemString checks that the String() function prints the right format, -// and that the body is printed without a nil-pointer panic. -func TestStItemString(t *testing.T) { - wantPrefix := map[StFormat]string{ - StFormatReserved: "Format(reserved)", - StFormatSignedTreeHeadV1: "Format(signed_tree_head_v1): &{TreeHead", - StFormatCosignedTreeHeadV1: "Format(cosigned_tree_head_v1): &{SignedTreeHead", - StFormatConsistencyProofV1: "Format(consistency_proof_v1): &{LogId", - StFormatInclusionProofV1: "Format(inclusion_proof_v1): &{LogId", - StFormatSignedChecksumV1: "Format(signed_checksum_v1): &{Data", - StFormat(1<<16 - 1): "unknown StItem: unknown StFormat: 65535", - } - tests := append(test_cases_stitem(t), testCaseSerialize{ - description: "valid: unknown StItem", - item: StItem{ - Format: StFormat(1<<16 - 1), - }, - }) - for _, table := range tests { - item, ok := table.item.(StItem) - if !ok { - t.Fatalf("must cast to StItem in test %q", table.description) - } - - prefix, ok := wantPrefix[item.Format] - if !ok { - t.Fatalf("must have prefix for StFormat %v in test %q", item.Format, table.description) - } - if got, want := item.String(), prefix; !strings.HasPrefix(got, want) { - t.Errorf("got %q but wanted prefix %q in test %q", got, want, table.description) - } - } -} - -// TODO: TestNewSignedTreeHeadV1 -func TestNewSignedTreeHeadV1(t *testing.T) { -} - -// TODO: TestNewCosignedTreeHeadV1 -func TestNewCosignedTreeHeadV1(t *testing.T) { -} - -// TODO: TestNewConsistencyProofV1 -func TestNewConsistencyProofV1(t *testing.T) { -} - -// TODO: TestNewInclusionProofV1 -func TestNewInclusionProofV1(t *testing.T) { -} - -// TODO: TestNewSignedChecksumV1 -func TestNewSignedChecksumV1(t *testing.T) { -} - -// TODO: TestNewTreeHeadV1 -func TestNewTreeHeadV1(t *testing.T) { -} diff --git a/types/trunnel.go b/types/trunnel.go new file mode 100644 index 0000000..72ae68d --- /dev/null +++ b/types/trunnel.go @@ -0,0 +1,57 @@ +package types + +import ( + "encoding/binary" + "fmt" +) + +const ( + // MessageSize is the number of bytes in a Trunnel-encoded leaf message + MessageSize = 8 + HashSize + // LeafSize is the number of bytes in a Trunnel-encoded leaf + LeafSize = MessageSize + SignatureSize + HashSize +) + +// Marshal returns a Trunnel-encoded message +func (m *Message) Marshal() []byte { + buf := make([]byte, MessageSize) + binary.BigEndian.PutUint64(buf, m.ShardHint) + copy(buf[8:], m.Checksum[:]) + return buf +} + +// Marshal returns a Trunnel-encoded leaf +func (l *Leaf) Marshal() []byte { + buf := l.Message.Marshal() + buf = append(buf, l.SigIdent.Signature[:]...) + buf = append(buf, l.SigIdent.KeyHash[:]...) + return buf +} + +// Marshal returns a Trunnel-encoded tree head +func (th *TreeHead) Marshal() []byte { + buf := make([]byte, 8+8+HashSize) + binary.BigEndian.PutUint64(buf[0:8], th.Timestamp) + binary.BigEndian.PutUint64(buf[8:16], th.TreeSize) + copy(buf[16:], th.RootHash[:]) + return buf +} + +// Unmarshal parses the Trunnel-encoded buffer as a leaf +func (l *Leaf) Unmarshal(buf []byte) error { + if len(buf) != LeafSize { + return fmt.Errorf("invalid leaf size: %v", len(buf)) + } + // Shard hint + l.ShardHint = binary.BigEndian.Uint64(buf) + offset := 8 + // Checksum + copy(l.Checksum[:], buf[offset:offset+HashSize]) + offset += HashSize + // Signature + copy(l.Signature[:], buf[offset:offset+SignatureSize]) + offset += SignatureSize + // KeyHash + copy(l.KeyHash[:], buf[offset:]) + return nil +} diff --git a/types/trunnel_test.go b/types/trunnel_test.go new file mode 100644 index 0000000..0fa7656 --- /dev/null +++ b/types/trunnel_test.go @@ -0,0 +1,114 @@ +package types + +import ( + "bytes" + "reflect" + "testing" +) + +var ( + testBuffer32 = [32]byte{0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31} + testBuffer64 = [64]byte{0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, 62, 63} +) + +func TestMarshalMessage(t *testing.T) { + description := "valid: shard hint 72623859790382856, checksum 0x00,0x01,..." + message := &Message{ + ShardHint: 72623859790382856, + Checksum: testBuffer32, + } + want := bytes.Join([][]byte{ + []byte{0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08}, + testBuffer32[:], + }, nil) + if got := message.Marshal(); !bytes.Equal(got, want) { + t.Errorf("got message\n\t%v\nbut wanted\n\t%v\nin test %q\n", got, want, description) + } +} + +func TestMarshalLeaf(t *testing.T) { + description := "valid: shard hint 72623859790382856, buffers 0x00,0x01,..." + leaf := &Leaf{ + Message: Message{ + ShardHint: 72623859790382856, + Checksum: testBuffer32, + }, + SigIdent: SigIdent{ + Signature: testBuffer64, + KeyHash: testBuffer32, + }, + } + want := bytes.Join([][]byte{ + []byte{0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08}, + testBuffer32[:], testBuffer64[:], testBuffer32[:], + }, nil) + if got := leaf.Marshal(); !bytes.Equal(got, want) { + t.Errorf("got leaf\n\t%v\nbut wanted\n\t%v\nin test %q\n", got, want, description) + } +} + +func TestMarshalTreeHead(t *testing.T) { + description := "valid: timestamp 16909060, tree size 72623859790382856, root hash 0x00,0x01,..." + th := &TreeHead{ + Timestamp: 16909060, + TreeSize: 72623859790382856, + RootHash: testBuffer32, + } + want := bytes.Join([][]byte{ + []byte{0x00, 0x00, 0x00, 0x00, 0x01, 0x02, 0x03, 0x04}, + []byte{0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08}, + testBuffer32[:], + }, nil) + if got := th.Marshal(); !bytes.Equal(got, want) { + t.Errorf("got tree head\n\t%v\nbut wanted\n\t%v\nin test %q\n", got, want, description) + } +} + +func TestUnmarshalLeaf(t *testing.T) { + for _, table := range []struct { + description string + serialized []byte + wantErr bool + want *Leaf + }{ + { + description: "invalid: not enough bytes", + serialized: make([]byte, LeafSize-1), + wantErr: true, + }, + { + description: "invalid: too many bytes", + serialized: make([]byte, LeafSize+1), + wantErr: true, + }, + { + description: "valid: shard hint 72623859790382856, buffers 0x00,0x01,...", + serialized: bytes.Join([][]byte{ + []byte{0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08}, + testBuffer32[:], testBuffer64[:], testBuffer32[:], + }, nil), + want: &Leaf{ + Message: Message{ + ShardHint: 72623859790382856, + Checksum: testBuffer32, + }, + SigIdent: SigIdent{ + Signature: testBuffer64, + KeyHash: testBuffer32, + }, + }, + }, + } { + var leaf Leaf + err := leaf.Unmarshal(table.serialized) + if got, want := err != nil, table.wantErr; got != want { + t.Errorf("got error %v but wanted %v in test %q: %v", got, want, table.description, err) + } + if err != nil { + continue // nothing more to check on error + } + if got, want := &leaf, table.want; !reflect.DeepEqual(got, want) { + t.Errorf("got leaf\n\t%v\nbut wanted\n\t%v\nin test %q\n", got, want, table.description) + } + } +} diff --git a/types/types.go b/types/types.go new file mode 100644 index 0000000..483dac0 --- /dev/null +++ b/types/types.go @@ -0,0 +1,73 @@ +package types + +import ( + "crypto/ed25519" + "crypto/sha256" +) + +const ( + HashSize = sha256.Size + SignatureSize = ed25519.SignatureSize +) + +// Leaf is the log's Merkle tree leaf. +// +// Ref: https://github.com/system-transparency/stfe/blob/design/doc/api.md#merkle-tree-leaf +type Leaf struct { + Message + SigIdent +} + +// Message is composed of a shard hint and a checksum. The submitter selects +// these values to fit the log's shard interval and the opaque data in question. +type Message struct { + ShardHint uint64 + Checksum [HashSize]byte +} + +// SigIdent is composed of a signature-signer pair. The signature is computed +// over the Trunnel-serialized leaf message. KeyHash identifies the signer. +type SigIdent struct { + Signature [SignatureSize]byte + KeyHash [HashSize]byte +} + +// SignedTreeHead is composed of a tree head and a list of signature-signer +// pairs. Each signature is computed over the Trunnel-serialized tree head. +// +// Ref: https://github.com/system-transparency/stfe/blob/design/doc/api.md#get-tree-head-cosigned +// Ref: https://github.com/system-transparency/stfe/blob/design/doc/api.md#get-tree-head-to-sign +// Ref: https://github.com/system-transparency/stfe/blob/design/doc/api.md#get-tree-head-latest +type SignedTreeHead struct { + TreeHead + SigIdent []SigIdent +} + +// TreeHead is the log's tree head. +// +// Ref: https://github.com/system-transparency/stfe/blob/design/doc/api.md#merkle-tree-head +type TreeHead struct { + Timestamp uint64 + TreeSize uint64 + RootHash [HashSize]byte +} + +// ConsistencyProof is a consistency proof that proves the log's append-only +// property. +// +// Ref: https://github.com/system-transparency/stfe/blob/design/doc/api.md#get-consistency-proof +type ConsistencyProof struct { + NewSize uint64 + OldSize uint64 + Path [][HashSize]byte +} + +// InclusionProof is an inclusion proof that proves a leaf is included in the +// log. +// +// Ref: https://github.com/system-transparency/stfe/blob/design/doc/api.md#get-proof-by-hash +type InclusionProof struct { + TreeSize uint64 + LeafIndex uint64 + Path [][HashSize]byte +} -- cgit v1.2.3 From 6a20aec8e8a93ce11f8b940659f49c889f94aef1 Mon Sep 17 00:00:00 2001 From: Rasmus Dahlberg Date: Tue, 18 May 2021 12:50:26 +0200 Subject: added ToHTTP methods Not unit tested yet. --- types/http.go | 45 +++++++++++++++++++++++++++++++++------------ types/http_test.go | 9 +++++++++ 2 files changed, 42 insertions(+), 12 deletions(-) diff --git a/types/http.go b/types/http.go index cc61d26..8bbe26d 100644 --- a/types/http.go +++ b/types/http.go @@ -54,27 +54,39 @@ func (sth *SignedTreeHead) ToHTTP() ([]byte, error) { hdr.Add(HeaderSignature, hex.EncodeToString(sigident.Signature[:])) hdr.Add(HeaderKeyHash, hex.EncodeToString(sigident.KeyHash[:])) } - - buf := bytes.NewBuffer(nil) - if err := hdr.Write(buf); err != nil { - return nil, fmt.Errorf("hdr.Write(): %v", err) // should not happen - } - return buf.Bytes(), nil + return headerToBytes(hdr) } // ToHTTP returns a consistency proof as HTTP key-value pairs -func (p *ConsistencyProof) ToHTTP() []byte { - return nil // TODO +func (p *ConsistencyProof) ToHTTP() ([]byte, error) { + hdr := http.Header{} + hdr.Add(HeaderNewSize, strconv.FormatUint(p.NewSize, 10)) + hdr.Add(HeaderOldSize, strconv.FormatUint(p.OldSize, 10)) + for _, hash := range p.Path { + hdr.Add(HeaderConsistencyPath, hex.EncodeToString(hash[:])) + } + return headerToBytes(hdr) } // ToHTTP returns an inclusion proof as HTTP key-value pairs -func (p *InclusionProof) ToHTTP() []byte { - return nil // TODO +func (p *InclusionProof) ToHTTP() ([]byte, error) { + hdr := http.Header{} + hdr.Add(HeaderTreeSize, strconv.FormatUint(p.TreeSize, 10)) + hdr.Add(HeaderLeafIndex, strconv.FormatUint(p.LeafIndex, 10)) + for _, hash := range p.Path { + hdr.Add(HeaderInclusionPath, hex.EncodeToString(hash[:])) + } + return headerToBytes(hdr) } // ToHTTP returns a leaf as HTTP key-value pairs -func (l *Leaf) ToHTTP() []byte { - return nil // TODO +func (l *Leaf) ToHTTP() ([]byte, error) { + hdr := http.Header{} + hdr.Add(HeaderShardHint, strconv.FormatUint(l.ShardHint, 10)) + hdr.Add(HeaderChecksum, hex.EncodeToString(l.Checksum[:])) + hdr.Add(HeaderSignature, hex.EncodeToString(l.Signature[:])) + hdr.Add(HeaderKeyHash, hex.EncodeToString(l.KeyHash[:])) + return headerToBytes(hdr) } // SignedTreeHeadFromHTTP parses a signed tree head from HTTP key-value pairs @@ -165,3 +177,12 @@ func decodeHex(str string, out []byte) error { copy(out, buf) return nil } + +// headerToBytes encodes a header as HTTP key-value pairs +func headerToBytes(hdr http.Header) ([]byte, error) { + buf := bytes.NewBuffer(nil) + if err := hdr.Write(buf); err != nil { + return nil, fmt.Errorf("hdr.Write(): %v", err) // should not happen + } + return buf.Bytes(), nil +} diff --git a/types/http_test.go b/types/http_test.go index 8165edf..527bcdf 100644 --- a/types/http_test.go +++ b/types/http_test.go @@ -57,6 +57,15 @@ func TestSignedTreeHeadToHTTP(t *testing.T) { compareHeaderWithMap(t, description, hdr, want) } +func TestConsistencyProofToHTTP(t *testing.T) { // TODO +} + +func TestInclusionProofToHTTP(t *testing.T) { // TODO +} + +func TestLeafToHTTP(t *testing.T) { // TODO +} + func TestSignedTreeHeadFromHTTP(t *testing.T) { for _, table := range []struct { description string -- cgit v1.2.3 From 533f683ef1ae999c2fdc0086cbc3de4e675d1e33 Mon Sep 17 00:00:00 2001 From: Linus Nordberg Date: Tue, 25 May 2021 11:26:32 +0200 Subject: use POST for requests with input data The major argument for moving input data from HTTP headers in GET requests to body of POST's is that we define the protocol ourselves without any dependencies on HTTP and can make it even simpler to parse. --- doc/api.md | 52 ++++++++++++++++++++++++++++++++-------------------- 1 file changed, 32 insertions(+), 20 deletions(-) diff --git a/doc/api.md b/doc/api.md index 4f43d2c..a998d70 100644 --- a/doc/api.md +++ b/doc/api.md @@ -11,11 +11,9 @@ This is a work-in-progress document that may be moved or modified. ## Overview The log implements an HTTP(S) API: -- Requests to the log use the HTTP GET method. -- Input data (in requests) and output data (in responses) are - expressed as ASCII-encoded key/value pairs. -- Requests use HTTP entity headers for input data while responses use - the HTTP message body for output data. +- Input data in requests and output data in responses are expressed as + ASCII-encoded key/value pairs. +- Requests with input data use POST to send the data to the log. - Binary data is hex-encoded before being transmitted. The motivation for using a text based key/value format for request and @@ -136,21 +134,17 @@ constraint is that it must be a valid HTTP(S) URL that can have the URL could be `https://log.example.com/2021/st/v0/get-signed-tree-head`. -Input data (in requests) is sent as ASCII key/value pairs as HTTP -entity headers, with their keys prefixed with the string -`stlog-`. Example: For sending `treee_size=4711` as input a client -would send the HTTP header `stlog-tree_size: 4711`. +Input data (in requests) is POST:ed in the HTTP message body as ASCII +key/value pairs. Output data (in replies) is sent in the HTTP message body in the same format as the input data, i.e. as ASCII key/value pairs on the format -`Key: Value`. Example: For sending `tree_size=4711` as output a log -would send an HTTP message body consisting of `stlog-tree_size: 4711`. +`Key=Value` The HTTP status code is 200 OK to indicate success. A different HTTP -status code is used to indicate failure. The log should set the value -value for the key `error` to a human-readable string describing what -went wrong. For example, `error: invalid signature`, `error: rate -limit exceeded`, or `error: unknown leaf hash`. +status code is used to indicate failure, in which case the log should +respond with a human-readable string describing what went wrong using +the key `error`. Example: `error=Invalid signature.`. ### get-tree-head-cosigned Returns the latest cosigned tree head. Used together with @@ -237,7 +231,7 @@ There is exactly one `signature` and one `key_hash` field. The ### get-proof-by-hash ``` -GET /st/v0/get-proof-by-hash +POST /st/v0/get-proof-by-hash ``` Input: @@ -260,9 +254,12 @@ other words, `SHA256(0x00 | tree_leaf)`. proof of zero or more node hashes. The order of node hashes follow from the hash strategy, see RFC 6962. +Example: `echo "leaf_hash=241fd4538d0a35c2d0394e4710ea9e6916854d08f62602fb03b55221dcdac90f +tree_size=4711" | curl --data-binary @- localhost/st/v0/get-proof-by-hash` + ### get-consistency-proof ``` -GET /st/v0/get-consistency-proof +POST /st/v0/get-consistency-proof ``` Input: @@ -283,9 +280,12 @@ Output on success: consistency proof of zero or more node hashes. The order of node hashes follow from the hash strategy, see RFC 6962. +Example: `echo "new_size=4711 +old_size=42" | curl --data-binary @- localhost/st/v0/get-consistency-proof` + ### get-leaves ``` -GET /st/v0/get-leaves +POST /st/v0/get-leaves ``` Input: @@ -309,9 +309,12 @@ match. The log may return fewer leaves than requested. At least one leaf must be returned on HTTP status code 200 OK. +Example: `echo "start_size=42 +end_size=4711" | curl --data-binary @- localhost/st/v0/get-leaves` + ### add-leaf ``` -GET /st/v0/add-leaf +POST /st/v0/add-leaf ``` Input: @@ -349,9 +352,15 @@ inclusion proof is available. An inclusion proof should not be relied upon unless it leads up to a trustworthy signed tree head. Witness cosigning can make a tree head trustworthy. +Example: `echo "shard_hint=1640995200 +checksum=cfa2d8e78bf273ab85d3cef7bde62716261d1e42626d776f9b4e6aae7b6ff953 +signature_over_message=c026687411dea494539516ee0c4e790c24450f1a4440c2eb74df311ca9a7adf2847b99273af78b0bda65dfe9c4f7d23a5d319b596a8881d3bc2964749ae9ece3 +verification_key=c9a674888e905db1761ba3f10f3ad09586dddfe8581964b55787b44f318cbcdf +domain_hint=example.com" | curl --data-binary @- localhost/st/v0/add-leaf` + ### add-cosignature ``` -GET /st/v0/add-cosignature +POST /st/v0/add-cosignature ``` Input: @@ -369,6 +378,9 @@ head. A key-hash, rather than the full verification key, is used to motivate verifiers to locate the appropriate key and make an explicit trust decision. +Example: `echo "signature=d1b15061d0f287847d066630339beaa0915a6bbb77332c3e839a32f66f1831b69c678e8ca63afd24e436525554dbc6daa3b1201cc0c93721de24b778027d41af +key_hash=662ce093682280f8fbea9939abe02fdba1f0dc39594c832b411ddafcffb75b1d" | curl --data-binary @- localhost/st/v0/add-cosignature` + ## Summary of log parameters - **Public key**: an Ed25519 verification key that can be used to verify the log's tree head signatures. -- cgit v1.2.3 From 8822e78af9fb67dc9280de08c2758350a862b8ab Mon Sep 17 00:00:00 2001 From: Linus Nordberg Date: Tue, 25 May 2021 12:14:45 +0200 Subject: replace some of "the log" and other rephrasing --- doc/api.md | 49 ++++++++++++++++++++++++++----------------------- 1 file changed, 26 insertions(+), 23 deletions(-) diff --git a/doc/api.md b/doc/api.md index a998d70..beda293 100644 --- a/doc/api.md +++ b/doc/api.md @@ -9,11 +9,12 @@ It can be found This is a work-in-progress document that may be moved or modified. ## Overview -The log implements an HTTP(S) API: +Logs implement an HTTP(S) API for accepting requests and sending +responses. - Input data in requests and output data in responses are expressed as ASCII-encoded key/value pairs. -- Requests with input data use POST to send the data to the log. +- Requests with input data use HTTP POST to send the data to a log. - Binary data is hex-encoded before being transmitted. The motivation for using a text based key/value format for request and @@ -27,12 +28,12 @@ wire-format in use by the Tor project. ## Primitives ### Cryptography -The log uses the same Merkle tree hash strategy as +Logs use the same Merkle tree hash strategy as [RFC 6962,§2](https://tools.ietf.org/html/rfc6962#section-2). The hash functions must be [SHA256](https://csrc.nist.gov/csrc/media/publications/fips/180/4/final/documents/fips180-4-draft-aug2014.pdf). -The log must sign tree heads using -[Ed25519](https://tools.ietf.org/html/rfc8032). The log's witnesses +Logs must sign tree heads using +[Ed25519](https://tools.ietf.org/html/rfc8032). Log witnesses must also sign tree heads using Ed25519. All other parts that are not Merkle tree related also use SHA256 as @@ -73,7 +74,7 @@ you may use it though. The main point of using Trunnel is that it makes a simple format explicit and unambiguous. #### Merkle tree head -Tree heads are signed by the log and its witnesses. It contains a +Tree heads are signed both by a log and its witnesses. It contains a timestamp, a tree size, and a root hash. The timestamp is included so that monitors can ensure _liveliness_. It is the time since the UNIX epoch (January 1, 1970 00:00 UTC) in seconds. The tree size @@ -93,7 +94,7 @@ not cosign a tree head if it is inconsistent with prior history or if the timestamp is backdated or future-dated more than 12 hours. #### Merkle tree leaf -The log supports a single leaf type. It contains a shard hint, a +Logs support a single leaf type. It contains a shard hint, a checksum over whatever the submitter wants to log a checksum for, a signature that the submitter computed over the shard hint and the checksum, and a hash of the submitter's public verification key, that @@ -113,8 +114,8 @@ struct tree_leaf { ``` `message` is composed of the `shard_hint`, chosen by the submitter to -match the shard interval for the log, and the submitter's `checksum` -to be logged. +match the shard interval for the log it's submitting to, and the +submitter's `checksum` to be logged. `signature_over_message` is a signature over `message`, using the submitter's verification key. It must be possible to verify the @@ -142,13 +143,13 @@ format as the input data, i.e. as ASCII key/value pairs on the format `Key=Value` The HTTP status code is 200 OK to indicate success. A different HTTP -status code is used to indicate failure, in which case the log should +status code is used to indicate failure, in which case a log should respond with a human-readable string describing what went wrong using the key `error`. Example: `error=Invalid signature.`. ### get-tree-head-cosigned Returns the latest cosigned tree head. Used together with -`get-proof-by-hash` and `get-consistency-proof` for verifying the log. +`get-proof-by-hash` and `get-consistency-proof` for verifying the tree. ``` GET /st/v0/get-tree-head-cosigned @@ -306,7 +307,7 @@ value in each list refers to the first leaf, the second value in each list refers to the second leaf, etc. The size of each list must match. -The log may return fewer leaves than requested. At least one leaf +A log may return fewer leaves than requested. At least one leaf must be returned on HTTP status code 200 OK. Example: `echo "start_size=42 @@ -340,11 +341,11 @@ match a hash over `verification_key`. The submission may also not be accepted if the second-level domain name exceeded its rate limit. By coupling every add-leaf request to -a second-level domain, it becomes more difficult to spam the log. You +a second-level domain, it becomes more difficult to spam logs. You would need an excessive number of domain names. This becomes costly if free domain names are rejected. -The log does not publish domain-name to key bindings because key +Logs don't publish domain-name to key bindings because key management is more complex than that. Public logging should not be assumed to have happened until an @@ -373,7 +374,7 @@ Input: Output on success: - None -`key_hash` can be used to identify which witness signed the log's tree +`key_hash` can be used to identify which witness signed the tree head. A key-hash, rather than the full verification key, is used to motivate verifiers to locate the appropriate key and make an explicit trust decision. @@ -382,11 +383,13 @@ Example: `echo "signature=d1b15061d0f287847d066630339beaa0915a6bbb77332c3e839a32 key_hash=662ce093682280f8fbea9939abe02fdba1f0dc39594c832b411ddafcffb75b1d" | curl --data-binary @- localhost/st/v0/add-cosignature` ## Summary of log parameters -- **Public key**: an Ed25519 verification key that can be used to - verify the log's tree head signatures. -- **Log identifier**: the hashed public verification key using SHA256. -- **Shard interval**: the time during which the log accepts logging - requests. The shard interval's start and end are inclusive and - expressed as the number of seconds since the UNIX epoch. -- **Base URL**: where the log can be reached over HTTP(S). It is the - prefix before a version-0 specific endpoint. +- **Public key**: The Ed25519 verification key to be used for + verifying tree head signatures. +- **Log identifier**: The public verification key `Public key` hashed + using SHA256. +- **Shard interval start**: The earliest time at which logging + requests are accepted as the number of seconds since the UNIX epoch. +- **Shard interval end**: The latest time at which logging + requests are accepted as the number of seconds since the UNIX epoch. +- **Base URL**: Where the log can be reached over HTTP(S). It is the + prefix to be used to construct a version 0 specific endpoint. -- cgit v1.2.3 From e374db9e70cd329ff46f1a4443c59a8fa118ddd6 Mon Sep 17 00:00:00 2001 From: Linus Nordberg Date: Fri, 28 May 2021 11:44:39 +0200 Subject: use a proper endpoint in example --- doc/api.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/doc/api.md b/doc/api.md index beda293..92344c5 100644 --- a/doc/api.md +++ b/doc/api.md @@ -133,7 +133,7 @@ Every log has a base URL that identifies it uniquely. The only constraint is that it must be a valid HTTP(S) URL that can have the `/st/v0/` suffix appended. For example, a complete endpoint URL could be -`https://log.example.com/2021/st/v0/get-signed-tree-head`. +`https://log.example.com/2021/st/v0/get-tree-head-cosigned`. Input data (in requests) is POST:ed in the HTTP message body as ASCII key/value pairs. -- cgit v1.2.3 From fe2e20f346e5f8a66c92016d77f32241498b790e Mon Sep 17 00:00:00 2001 From: Linus Nordberg Date: Fri, 28 May 2021 11:44:54 +0200 Subject: clarify what the signature in get-tree-head-* is covering --- doc/api.md | 15 +++++++++------ 1 file changed, 9 insertions(+), 6 deletions(-) diff --git a/doc/api.md b/doc/api.md index 92344c5..57ad119 100644 --- a/doc/api.md +++ b/doc/api.md @@ -163,8 +163,9 @@ Output on success: seconds since the UNIX epoch. - `tree_size`: `tree_head.tree_size` ASCII-encoded decimal number. - `root_hash`: `tree_head.root_hash` hex-encoded. -- `signature`: hex-encoded Ed25519 signature over `tree_head` - serialzed as described in section `Merkle tree head`. +- `signature`: hex-encoded Ed25519 signature over `timestamp`, + `tree_size` and `root_hash` serialized into a `tree_head` as + described in section `Merkle tree head`. - `key_hash`: a hash of the public verification key (belonging to either the log or to one of its witnesses), which can be used to verify the most recent `signature`. The key is encoded as defined @@ -192,8 +193,9 @@ Output on success: seconds since the UNIX epoch. - `tree_size`: `tree_head.tree_size` ASCII-encoded decimal number. - `root_hash`: `tree_head.root_hash` hex-encoded. -- `signature`: hex-encoded Ed25519 signature over `tree_head` - serialzed as described in section `Merkle tree head`. +- `signature`: hex-encoded Ed25519 signature over `timestamp`, + `tree_size` and `root_hash` serialized into a `tree_head` as + described in section `Merkle tree head`. - `key_hash`: a hash of the log's public verification key, which can be used to verify `signature`. The key is encoded as defined in [RFC 8032, section 5.1.2](https://tools.ietf.org/html/rfc8032#section-5.1.2), @@ -219,8 +221,9 @@ Output on success: seconds since the UNIX epoch. - `tree_size`: `tree_head.tree_size` ASCII-encoded decimal number. - `root_hash`: `tree_head.root_hash` hex-encoded. -- `signature`: hex-encoded Ed25519 signature over `tree_head` - serialzed as described in section `Merkle tree head`. +- `signature`: hex-encoded Ed25519 signature over `timestamp`, + `tree_size` and `root_hash` serialized into a `tree_head` as + described in section `Merkle tree head`. - `key_hash`: a hash of the log's public verification key that can be used to verify `signature`. The key is encoded as defined in [RFC 8032, section 5.1.2](https://tools.ietf.org/html/rfc8032#section-5.1.2), -- cgit v1.2.3 From f3465d2088f54e49c4939137116d23e5e26c3d22 Mon Sep 17 00:00:00 2001 From: Rasmus Dahlberg Date: Mon, 31 May 2021 13:34:04 +0200 Subject: added (un)marshal methods --- types/ascii.go | 413 ++++++++++++++++++++++++++++++++++++++++++++ types/ascii_test.go | 465 ++++++++++++++++++++++++++++++++++++++++++++++++++ types/http.go | 188 -------------------- types/http_test.go | 331 ----------------------------------- types/trunnel.go | 3 + types/trunnel_test.go | 6 +- types/types.go | 63 ++++++- 7 files changed, 938 insertions(+), 531 deletions(-) create mode 100644 types/ascii.go create mode 100644 types/ascii_test.go delete mode 100644 types/http.go delete mode 100644 types/http_test.go diff --git a/types/ascii.go b/types/ascii.go new file mode 100644 index 0000000..88b372e --- /dev/null +++ b/types/ascii.go @@ -0,0 +1,413 @@ +package types + +import ( + "bytes" + "encoding/hex" + "fmt" + "io" + "strconv" +) + +const ( + // Delim is a key-value separator + Delim = "=" + + // EOL is a line sepator + EOL = "\n" + + // NumField* is the number of unique keys in an incoming ASCII message + NumFieldLeaf = 4 + NumFieldSignedTreeHead = 5 + NumFieldConsistencyProof = 3 + NumFieldInclusionProof = 3 + NumFieldLeavesRequest = 2 + NumFieldInclusionProofRequest = 2 + NumFieldConsistencyProofRequest = 2 + NumFieldLeafRequest = 5 + NumFieldCosignatureRequest = 2 + + // New leaf keys + ShardHint = "shard_hint" + Checksum = "checksum" + SignatureOverMessage = "signature_over_message" + VerificationKey = "verification_key" + DomainHint = "domain_hint" + + // Inclusion proof keys + LeafHash = "leaf_hash" + LeafIndex = "leaf_index" + InclusionPath = "inclusion_path" + + // Consistency proof keys + NewSize = "new_size" + OldSize = "old_size" + ConsistencyPath = "consistency_path" + + // Range of leaves keys + StartSize = "start_size" + EndSize = "end_size" + + // Tree head keys + Timestamp = "timestamp" + TreeSize = "tree_size" + RootHash = "root_hash" + + // Signature and signer-identity keys + Signature = "signature" + KeyHash = "key_hash" +) + +// MessageASCI is a wrapper that manages ASCII key-value pairs +type MessageASCII struct { + m map[string][]string +} + +// NewMessageASCII unpacks an incoming ASCII message +func NewMessageASCII(r io.Reader, numFieldExpected int) (*MessageASCII, error) { + buf, err := io.ReadAll(r) + if err != nil { + return nil, fmt.Errorf("ReadAll: %v", err) + } + lines := bytes.Split(buf, []byte(EOL)) + if len(lines) <= 1 { + return nil, fmt.Errorf("Not enough lines: empty") + } + lines = lines[:len(lines)-1] // valid message => split gives empty last line + + msg := MessageASCII{make(map[string][]string)} + for _, line := range lines { + split := bytes.Index(line, []byte(Delim)) + if split == -1 { + return nil, fmt.Errorf("invalid line: %v", string(line)) + } + + key := string(line[:split]) + value := string(line[split+len(Delim):]) + values, ok := msg.m[key] + if !ok { + values = nil + msg.m[key] = values + } + msg.m[key] = append(values, value) + } + + if msg.NumField() != numFieldExpected { + return nil, fmt.Errorf("Unexpected number of keys: %v", msg.NumField()) + } + return &msg, nil +} + +// NumField returns the number of unique keys +func (msg *MessageASCII) NumField() int { + return len(msg.m) +} + +// GetStrings returns a list of strings +func (msg *MessageASCII) GetStrings(key string) []string { + strs, ok := msg.m[key] + if !ok { + return nil + } + return strs +} + +// GetString unpacks a string +func (msg *MessageASCII) GetString(key string) (string, error) { + strs := msg.GetStrings(key) + if len(strs) != 1 { + return "", fmt.Errorf("expected one string: %v", strs) + } + return strs[0], nil +} + +// GetUint64 unpacks an uint64 +func (msg *MessageASCII) GetUint64(key string) (uint64, error) { + str, err := msg.GetString(key) + if err != nil { + return 0, fmt.Errorf("GetString: %v", err) + } + num, err := strconv.ParseUint(str, 10, 64) + if err != nil { + return 0, fmt.Errorf("ParseUint: %v", err) + } + return num, nil +} + +// GetHash unpacks a hash +func (msg *MessageASCII) GetHash(key string) (*[HashSize]byte, error) { + str, err := msg.GetString(key) + if err != nil { + return nil, fmt.Errorf("GetString: %v", err) + } + + var hash [HashSize]byte + if err := decodeHex(str, hash[:]); err != nil { + return nil, fmt.Errorf("decodeHex: %v", err) + } + return &hash, nil +} + +// GetSignature unpacks a signature +func (msg *MessageASCII) GetSignature(key string) (*[SignatureSize]byte, error) { + str, err := msg.GetString(key) + if err != nil { + return nil, fmt.Errorf("GetString: %v", err) + } + + var signature [SignatureSize]byte + if err := decodeHex(str, signature[:]); err != nil { + return nil, fmt.Errorf("decodeHex: %v", err) + } + return &signature, nil +} + +// GetVerificationKey unpacks a verification key +func (msg *MessageASCII) GetVerificationKey(key string) (*[VerificationKeySize]byte, error) { + str, err := msg.GetString(key) + if err != nil { + return nil, fmt.Errorf("GetString: %v", err) + } + + var vk [VerificationKeySize]byte + if err := decodeHex(str, vk[:]); err != nil { + return nil, fmt.Errorf("decodeHex: %v", err) + } + return &vk, nil +} + +// decodeHex decodes a hex-encoded string into an already-sized byte slice +func decodeHex(str string, out []byte) error { + buf, err := hex.DecodeString(str) + if err != nil { + return fmt.Errorf("DecodeString: %v", err) + } + if len(buf) != len(out) { + return fmt.Errorf("invalid length: %v", len(buf)) + } + copy(out, buf) + return nil +} + +/* + * + * MarshalASCII wrappers for types that the log server outputs + * + */ +func (l *Leaf) MarshalASCII(w io.Writer) error { + if err := writeASCII(w, ShardHint, strconv.FormatUint(l.ShardHint, 10)); err != nil { + return fmt.Errorf("writeASCII: %v", err) + } + if err := writeASCII(w, Checksum, hex.EncodeToString(l.Checksum[:])); err != nil { + return fmt.Errorf("writeASCII: %v", err) + } + if err := writeASCII(w, Signature, hex.EncodeToString(l.Signature[:])); err != nil { + return fmt.Errorf("writeASCII: %v", err) + } + if err := writeASCII(w, KeyHash, hex.EncodeToString(l.KeyHash[:])); err != nil { + return fmt.Errorf("writeASCII: %v", err) + } + return nil +} + +func (sth *SignedTreeHead) MarshalASCII(w io.Writer) error { + if err := writeASCII(w, Timestamp, strconv.FormatUint(sth.Timestamp, 10)); err != nil { + return fmt.Errorf("writeASCII: %v", err) + } + if err := writeASCII(w, TreeSize, strconv.FormatUint(sth.TreeSize, 10)); err != nil { + return fmt.Errorf("writeASCII: %v", err) + } + if err := writeASCII(w, RootHash, hex.EncodeToString(sth.RootHash[:])); err != nil { + return fmt.Errorf("writeASCII: %v", err) + } + for _, sigident := range sth.SigIdent { + if err := writeASCII(w, Signature, hex.EncodeToString(sigident.Signature[:])); err != nil { + return fmt.Errorf("writeASCII: %v", err) + } + if err := writeASCII(w, KeyHash, hex.EncodeToString(sigident.KeyHash[:])); err != nil { + return fmt.Errorf("writeASCII: %v", err) + } + } + return nil +} + +func (p *ConsistencyProof) MarshalASCII(w io.Writer) error { + if err := writeASCII(w, NewSize, strconv.FormatUint(p.NewSize, 10)); err != nil { + return fmt.Errorf("writeASCII: %v", err) + } + if err := writeASCII(w, OldSize, strconv.FormatUint(p.OldSize, 10)); err != nil { + return fmt.Errorf("writeASCII: %v", err) + } + for _, hash := range p.Path { + if err := writeASCII(w, ConsistencyPath, hex.EncodeToString(hash[:])); err != nil { + return fmt.Errorf("writeASCII: %v", err) + } + } + return nil +} + +func (p *InclusionProof) MarshalASCII(w io.Writer) error { + if err := writeASCII(w, TreeSize, strconv.FormatUint(p.TreeSize, 10)); err != nil { + return fmt.Errorf("writeASCII: %v", err) + } + if err := writeASCII(w, LeafIndex, strconv.FormatUint(p.LeafIndex, 10)); err != nil { + return fmt.Errorf("writeASCII: %v", err) + } + for _, hash := range p.Path { + if err := writeASCII(w, InclusionPath, hex.EncodeToString(hash[:])); err != nil { + return fmt.Errorf("writeASCII: %v", err) + } + } + return nil +} + +func writeASCII(w io.Writer, key, value string) error { + if _, err := fmt.Fprintf(w, "%s%s%s%s", key, Delim, value, EOL); err != nil { + return fmt.Errorf("Fprintf: %v", err) + } + return nil +} + +/* + * + * Unmarshal ASCII wrappers that the log server and/or log clients receive. + * + */ +func (ll *LeafList) UnmarshalASCII(r io.Reader) error { + return nil +} + +func (sth *SignedTreeHead) UnmarshalASCII(r io.Reader) error { + msg, err := NewMessageASCII(r, NumFieldSignedTreeHead) + if err != nil { + return fmt.Errorf("NewMessageASCII: %v", err) + } + + // TreeHead + if sth.Timestamp, err = msg.GetUint64(Timestamp); err != nil { + return fmt.Errorf("GetUint64(Timestamp): %v", err) + } + if sth.TreeSize, err = msg.GetUint64(TreeSize); err != nil { + return fmt.Errorf("GetUint64(TreeSize): %v", err) + } + if sth.RootHash, err = msg.GetHash(RootHash); err != nil { + return fmt.Errorf("GetHash(RootHash): %v", err) + } + + // SigIdent + signatures := msg.GetStrings(Signature) + if len(signatures) == 0 { + return fmt.Errorf("no signer") + } + keyHashes := msg.GetStrings(KeyHash) + if len(signatures) != len(keyHashes) { + return fmt.Errorf("mismatched signature-signer count") + } + sth.SigIdent = make([]*SigIdent, 0, len(signatures)) + for i, n := 0, len(signatures); i < n; i++ { + var signature [SignatureSize]byte + if err := decodeHex(signatures[i], signature[:]); err != nil { + return fmt.Errorf("decodeHex: %v", err) + } + var hash [HashSize]byte + if err := decodeHex(keyHashes[i], hash[:]); err != nil { + return fmt.Errorf("decodeHex: %v", err) + } + sth.SigIdent = append(sth.SigIdent, &SigIdent{ + Signature: &signature, + KeyHash: &hash, + }) + } + return nil +} + +func (p *InclusionProof) UnmarshalASCII(r io.Reader) error { + return nil +} + +func (p *ConsistencyProof) UnmarshalASCII(r io.Reader) error { + return nil +} + +func (req *InclusionProofRequest) UnmarshalASCII(r io.Reader) error { + msg, err := NewMessageASCII(r, NumFieldInclusionProofRequest) + if err != nil { + return fmt.Errorf("NewMessageASCII: %v", err) + } + + if req.LeafHash, err = msg.GetHash(LeafHash); err != nil { + return fmt.Errorf("GetHash(LeafHash): %v", err) + } + if req.TreeSize, err = msg.GetUint64(TreeSize); err != nil { + return fmt.Errorf("GetUint64(TreeSize): %v", err) + } + return nil +} + +func (req *ConsistencyProofRequest) UnmarshalASCII(r io.Reader) error { + msg, err := NewMessageASCII(r, NumFieldConsistencyProofRequest) + if err != nil { + return fmt.Errorf("NewMessageASCII: %v", err) + } + + if req.NewSize, err = msg.GetUint64(NewSize); err != nil { + return fmt.Errorf("GetUint64(NewSize): %v", err) + } + if req.OldSize, err = msg.GetUint64(OldSize); err != nil { + return fmt.Errorf("GetUint64(OldSize): %v", err) + } + return nil +} + +func (req *LeavesRequest) UnmarshalASCII(r io.Reader) error { + msg, err := NewMessageASCII(r, NumFieldLeavesRequest) + if err != nil { + return fmt.Errorf("NewMessageASCII: %v", err) + } + + if req.StartSize, err = msg.GetUint64(StartSize); err != nil { + return fmt.Errorf("GetUint64(StartSize): %v", err) + } + if req.EndSize, err = msg.GetUint64(EndSize); err != nil { + return fmt.Errorf("GetUint64(EndSize): %v", err) + } + return nil +} + +func (req *LeafRequest) UnmarshalASCII(r io.Reader) error { + msg, err := NewMessageASCII(r, NumFieldLeafRequest) + if err != nil { + return fmt.Errorf("NewMessageASCII: %v", err) + } + + if req.ShardHint, err = msg.GetUint64(ShardHint); err != nil { + return fmt.Errorf("GetUint64(ShardHint): %v", err) + } + if req.Checksum, err = msg.GetHash(Checksum); err != nil { + return fmt.Errorf("GetHash(Checksum): %v", err) + } + if req.Signature, err = msg.GetSignature(Signature); err != nil { + return fmt.Errorf("GetSignature: %v", err) + } + if req.VerificationKey, err = msg.GetVerificationKey(VerificationKey); err != nil { + return fmt.Errorf("GetVerificationKey: %v", err) + } + if req.DomainHint, err = msg.GetString(DomainHint); err != nil { + return fmt.Errorf("GetString(DomainHint): %v", err) + } + return nil +} + +func (req *CosignatureRequest) UnmarshalASCII(r io.Reader) error { + msg, err := NewMessageASCII(r, NumFieldCosignatureRequest) + if err != nil { + return fmt.Errorf("NewMessageASCII: %v", err) + } + + if req.Signature, err = msg.GetSignature(Signature); err != nil { + return fmt.Errorf("GetSignature: %v", err) + } + if req.KeyHash, err = msg.GetHash(KeyHash); err != nil { + return fmt.Errorf("GetHash(KeyHash): %v", err) + } + return nil +} diff --git a/types/ascii_test.go b/types/ascii_test.go new file mode 100644 index 0000000..74a1e37 --- /dev/null +++ b/types/ascii_test.go @@ -0,0 +1,465 @@ +package types + +import ( + "bytes" + "fmt" + "io" + "reflect" + "testing" +) + +/* + * + * MessageASCII methods and helpers + * + */ +func TestNewMessageASCII(t *testing.T) { + for _, table := range []struct { + description string + input io.Reader + wantErr bool + wantMap map[string][]string + }{ + { + description: "invalid: not enough lines", + input: bytes.NewBufferString(""), + wantErr: true, + }, + { + description: "invalid: lines must end with new line", + input: bytes.NewBufferString("k1=v1\nk2=v2"), + wantErr: true, + }, + { + description: "invalid: lines must not be empty", + input: bytes.NewBufferString("k1=v1\n\nk2=v2\n"), + wantErr: true, + }, + { + description: "invalid: wrong number of fields", + input: bytes.NewBufferString("k1=v1\n"), + wantErr: true, + }, + { + description: "valid", + input: bytes.NewBufferString("k1=v1\nk2=v2\nk2=v3=4\n"), + wantMap: map[string][]string{ + "k1": []string{"v1"}, + "k2": []string{"v2", "v3=4"}, + }, + }, + } { + msg, err := NewMessageASCII(table.input, len(table.wantMap)) + if got, want := err != nil, table.wantErr; got != want { + t.Errorf("got error %v but wanted %v in test %q: %v", got, want, table.description, err) + } + if err != nil { + continue + } + if got, want := msg.m, table.wantMap; !reflect.DeepEqual(got, want) { + t.Errorf("got\n\t%v\nbut wanted\n\t%v\nin test %q", got, want, table.description) + } + } +} + +func TestNumField(t *testing.T) {} +func TestGetStrings(t *testing.T) {} +func TestGetString(t *testing.T) {} +func TestGetUint64(t *testing.T) {} +func TestGetHash(t *testing.T) {} +func TestGetSignature(t *testing.T) {} +func TestGetVerificationKey(t *testing.T) {} +func TestDecodeHex(t *testing.T) {} + +/* + * + * MarshalASCII methods and helpers + * + */ +func TestLeafMarshalASCII(t *testing.T) { + description := "valid: two leaves" + leafList := []*Leaf{ + &Leaf{ + Message: Message{ + ShardHint: 123, + Checksum: testBuffer32, + }, + SigIdent: SigIdent{ + Signature: testBuffer64, + KeyHash: testBuffer32, + }, + }, + &Leaf{ + Message: Message{ + ShardHint: 456, + Checksum: testBuffer32, + }, + SigIdent: SigIdent{ + Signature: testBuffer64, + KeyHash: testBuffer32, + }, + }, + } + wantBuf := bytes.NewBufferString(fmt.Sprintf( + "%s%s%d%s"+"%s%s%x%s"+"%s%s%x%s"+"%s%s%x%s"+ + "%s%s%d%s"+"%s%s%x%s"+"%s%s%x%s"+"%s%s%x%s", + // Leaf 1 + ShardHint, Delim, 123, EOL, + Checksum, Delim, testBuffer32[:], EOL, + Signature, Delim, testBuffer64[:], EOL, + KeyHash, Delim, testBuffer32[:], EOL, + // Leaf 2 + ShardHint, Delim, 456, EOL, + Checksum, Delim, testBuffer32[:], EOL, + Signature, Delim, testBuffer64[:], EOL, + KeyHash, Delim, testBuffer32[:], EOL, + )) + buf := bytes.NewBuffer(nil) + for _, leaf := range leafList { + if err := leaf.MarshalASCII(buf); err != nil { + t.Errorf("expected error %v but got %v in test %q: %v", false, true, description, err) + return + } + } + if got, want := buf.Bytes(), wantBuf.Bytes(); !bytes.Equal(got, want) { + t.Errorf("got\n\t%v\nbut wanted\n\t%v\nin test %q", string(got), string(want), description) + } +} + +func TestSignedTreeHeadMarshalASCII(t *testing.T) { + description := "valid" + sth := &SignedTreeHead{ + TreeHead: TreeHead{ + Timestamp: 123, + TreeSize: 456, + RootHash: testBuffer32, + }, + SigIdent: []*SigIdent{ + &SigIdent{ + Signature: testBuffer64, + KeyHash: testBuffer32, + }, + &SigIdent{ + Signature: testBuffer64, + KeyHash: testBuffer32, + }, + }, + } + wantBuf := bytes.NewBufferString(fmt.Sprintf( + "%s%s%d%s"+"%s%s%d%s"+"%s%s%x%s"+"%s%s%x%s"+"%s%s%x%s"+"%s%s%x%s"+"%s%s%x%s", + Timestamp, Delim, 123, EOL, + TreeSize, Delim, 456, EOL, + RootHash, Delim, testBuffer32[:], EOL, + Signature, Delim, testBuffer64[:], EOL, + KeyHash, Delim, testBuffer32[:], EOL, + Signature, Delim, testBuffer64[:], EOL, + KeyHash, Delim, testBuffer32[:], EOL, + )) + buf := bytes.NewBuffer(nil) + if err := sth.MarshalASCII(buf); err != nil { + t.Errorf("expected error %v but got %v in test %q", false, true, description) + return + } + if got, want := buf.Bytes(), wantBuf.Bytes(); !bytes.Equal(got, want) { + t.Errorf("got\n\t%v\nbut wanted\n\t%v\nin test %q", string(got), string(want), description) + } +} + +func TestInclusionProofMarshalASCII(t *testing.T) { + description := "valid" + proof := InclusionProof{ + TreeSize: 321, + LeafIndex: 123, + Path: []*[HashSize]byte{ + testBuffer32, + testBuffer32, + }, + } + wantBuf := bytes.NewBufferString(fmt.Sprintf( + "%s%s%d%s"+"%s%s%d%s"+"%s%s%x%s"+"%s%s%x%s", + TreeSize, Delim, 321, EOL, + LeafIndex, Delim, 123, EOL, + InclusionPath, Delim, testBuffer32[:], EOL, + InclusionPath, Delim, testBuffer32[:], EOL, + )) + buf := bytes.NewBuffer(nil) + if err := proof.MarshalASCII(buf); err != nil { + t.Errorf("expected error %v but got %v in test %q", false, true, description) + return + } + if got, want := buf.Bytes(), wantBuf.Bytes(); !bytes.Equal(got, want) { + t.Errorf("got\n\t%v\nbut wanted\n\t%v\nin test %q", string(got), string(want), description) + } +} + +func TestConsistencyProofMarshalASCII(t *testing.T) { + description := "valid" + proof := ConsistencyProof{ + NewSize: 321, + OldSize: 123, + Path: []*[HashSize]byte{ + testBuffer32, + testBuffer32, + }, + } + wantBuf := bytes.NewBufferString(fmt.Sprintf( + "%s%s%d%s"+"%s%s%d%s"+"%s%s%x%s"+"%s%s%x%s", + NewSize, Delim, 321, EOL, + OldSize, Delim, 123, EOL, + ConsistencyPath, Delim, testBuffer32[:], EOL, + ConsistencyPath, Delim, testBuffer32[:], EOL, + )) + buf := bytes.NewBuffer(nil) + if err := proof.MarshalASCII(buf); err != nil { + t.Errorf("expected error %v but got %v in test %q", false, true, description) + return + } + if got, want := buf.Bytes(), wantBuf.Bytes(); !bytes.Equal(got, want) { + t.Errorf("got\n\t%v\nbut wanted\n\t%v\nin test %q", string(got), string(want), description) + } +} + +func TestWriteASCII(t *testing.T) { +} + +/* + * + * UnmarshalASCII methods and helpers + * + */ +func TestLeafListUnmarshalASCII(t *testing.T) {} + +func TestSignedTreeHeadUnmarshalASCII(t *testing.T) { + for _, table := range []struct { + description string + buf io.Reader + wantErr bool + wantSth *SignedTreeHead + }{ + { + description: "valid", + buf: bytes.NewBufferString(fmt.Sprintf( + "%s%s%d%s"+"%s%s%d%s"+"%s%s%x%s"+"%s%s%x%s"+"%s%s%x%s"+"%s%s%x%s"+"%s%s%x%s", + Timestamp, Delim, 123, EOL, + TreeSize, Delim, 456, EOL, + RootHash, Delim, testBuffer32[:], EOL, + Signature, Delim, testBuffer64[:], EOL, + KeyHash, Delim, testBuffer32[:], EOL, + Signature, Delim, testBuffer64[:], EOL, + KeyHash, Delim, testBuffer32[:], EOL, + )), + wantSth: &SignedTreeHead{ + TreeHead: TreeHead{ + Timestamp: 123, + TreeSize: 456, + RootHash: testBuffer32, + }, + SigIdent: []*SigIdent{ + &SigIdent{ + Signature: testBuffer64, + KeyHash: testBuffer32, + }, + &SigIdent{ + Signature: testBuffer64, + KeyHash: testBuffer32, + }, + }, + }, + }, + } { + var sth SignedTreeHead + err := sth.UnmarshalASCII(table.buf) + if got, want := err != nil, table.wantErr; got != want { + t.Errorf("got error %v but wanted %v in test %q: %v", got, want, table.description, err) + } + if err != nil { + continue + } + if got, want := &sth, table.wantSth; !reflect.DeepEqual(got, want) { + t.Errorf("got\n\t%v\nbut wanted\n\t%v\nin test %q", got, want, table.description) + } + } +} + +func TestInclusionProofUnmarshalASCII(t *testing.T) {} +func TestConsistencyProofUnmarshalASCII(t *testing.T) {} + +func TestInclusionProofRequestUnmarshalASCII(t *testing.T) { + for _, table := range []struct { + description string + buf io.Reader + wantErr bool + wantReq *InclusionProofRequest + }{ + { + description: "valid", + buf: bytes.NewBufferString(fmt.Sprintf( + "%s%s%x%s"+"%s%s%d%s", + LeafHash, Delim, testBuffer32[:], EOL, + TreeSize, Delim, 123, EOL, + )), + wantReq: &InclusionProofRequest{ + LeafHash: testBuffer32, + TreeSize: 123, + }, + }, + } { + var req InclusionProofRequest + err := req.UnmarshalASCII(table.buf) + if got, want := err != nil, table.wantErr; got != want { + t.Errorf("got error %v but wanted %v in test %q: %v", got, want, table.description, err) + } + if err != nil { + continue + } + if got, want := &req, table.wantReq; !reflect.DeepEqual(got, want) { + t.Errorf("got\n\t%v\nbut wanted\n\t%v\nin test %q", got, want, table.description) + } + } +} + +func TestConsistencyProofRequestUnmarshalASCII(t *testing.T) { + for _, table := range []struct { + description string + buf io.Reader + wantErr bool + wantReq *ConsistencyProofRequest + }{ + { + description: "valid", + buf: bytes.NewBufferString(fmt.Sprintf( + "%s%s%d%s"+"%s%s%d%s", + NewSize, Delim, 321, EOL, + OldSize, Delim, 123, EOL, + )), + wantReq: &ConsistencyProofRequest{ + NewSize: 321, + OldSize: 123, + }, + }, + } { + var req ConsistencyProofRequest + err := req.UnmarshalASCII(table.buf) + if got, want := err != nil, table.wantErr; got != want { + t.Errorf("got error %v but wanted %v in test %q: %v", got, want, table.description, err) + } + if err != nil { + continue + } + if got, want := &req, table.wantReq; !reflect.DeepEqual(got, want) { + t.Errorf("got\n\t%v\nbut wanted\n\t%v\nin test %q", got, want, table.description) + } + } +} + +func TestLeavesRequestUnmarshalASCII(t *testing.T) { + for _, table := range []struct { + description string + buf io.Reader + wantErr bool + wantReq *LeavesRequest + }{ + { + description: "valid", + buf: bytes.NewBufferString(fmt.Sprintf( + "%s%s%d%s"+"%s%s%d%s", + StartSize, Delim, 123, EOL, + EndSize, Delim, 456, EOL, + )), + wantReq: &LeavesRequest{ + StartSize: 123, + EndSize: 456, + }, + }, + } { + var req LeavesRequest + err := req.UnmarshalASCII(table.buf) + if got, want := err != nil, table.wantErr; got != want { + t.Errorf("got error %v but wanted %v in test %q: %v", got, want, table.description, err) + } + if err != nil { + continue + } + if got, want := &req, table.wantReq; !reflect.DeepEqual(got, want) { + t.Errorf("got\n\t%v\nbut wanted\n\t%v\nin test %q", got, want, table.description) + } + } +} + +func TestLeafRequestUnmarshalASCII(t *testing.T) { + for _, table := range []struct { + description string + buf io.Reader + wantErr bool + wantReq *LeafRequest + }{ + { + description: "valid", + buf: bytes.NewBufferString(fmt.Sprintf( + "%s%s%d%s"+"%s%s%x%s"+"%s%s%x%s"+"%s%s%x%s"+"%s%s%s%s", + ShardHint, Delim, 123, EOL, + Checksum, Delim, testBuffer32[:], EOL, + Signature, Delim, testBuffer64[:], EOL, + VerificationKey, Delim, testBuffer32[:], EOL, + DomainHint, Delim, "example.com", EOL, + )), + wantReq: &LeafRequest{ + Message: Message{ + ShardHint: 123, + Checksum: testBuffer32, + }, + Signature: testBuffer64, + VerificationKey: testBuffer32, + DomainHint: "example.com", + }, + }, + } { + var req LeafRequest + err := req.UnmarshalASCII(table.buf) + if got, want := err != nil, table.wantErr; got != want { + t.Errorf("got error %v but wanted %v in test %q: %v", got, want, table.description, err) + } + if err != nil { + continue + } + if got, want := &req, table.wantReq; !reflect.DeepEqual(got, want) { + t.Errorf("got\n\t%v\nbut wanted\n\t%v\nin test %q", got, want, table.description) + } + } +} + +func TestCosignatureRequestUnmarshalASCII(t *testing.T) { + for _, table := range []struct { + description string + buf io.Reader + wantErr bool + wantReq *CosignatureRequest + }{ + { + description: "valid", + buf: bytes.NewBufferString(fmt.Sprintf( + "%s%s%x%s"+"%s%s%x%s", + Signature, Delim, testBuffer64[:], EOL, + KeyHash, Delim, testBuffer32[:], EOL, + )), + wantReq: &CosignatureRequest{ + SigIdent: SigIdent{ + Signature: testBuffer64, + KeyHash: testBuffer32, + }, + }, + }, + } { + var req CosignatureRequest + err := req.UnmarshalASCII(table.buf) + if got, want := err != nil, table.wantErr; got != want { + t.Errorf("got error %v but wanted %v in test %q: %v", got, want, table.description, err) + } + if err != nil { + continue + } + if got, want := &req, table.wantReq; !reflect.DeepEqual(got, want) { + t.Errorf("got\n\t%v\nbut wanted\n\t%v\nin test %q", got, want, table.description) + } + } +} diff --git a/types/http.go b/types/http.go deleted file mode 100644 index 8bbe26d..0000000 --- a/types/http.go +++ /dev/null @@ -1,188 +0,0 @@ -package types - -import ( - "bytes" - "encoding/hex" - "fmt" - "net/http" - "strconv" - "strings" -) - -const ( - // HeaderPrefix is the start of every ST log HTTP header key - HeaderPrefix = "stlog-" - - // New leaf - HeaderShardHint = HeaderPrefix + "shard_hint" - HeaderChecksum = HeaderPrefix + "checksum" - HeaderSignatureOverMessage = HeaderPrefix + "signature_over_message" - HeaderVerificationKey = HeaderPrefix + "verification_key" - HeaderDomainHint = HeaderPrefix + "domain_hint" - - // Inclusion proof - HeaderLeafHash = HeaderPrefix + "leaf_hash" - HeaderLeafIndex = HeaderPrefix + "leaf_index" - HeaderInclusionPath = HeaderPrefix + "inclusion_path" - - // Consistency proof - HeaderNewSize = HeaderPrefix + "new_size" - HeaderOldSize = HeaderPrefix + "old_size" - HeaderConsistencyPath = HeaderPrefix + "consistency_path" - - // Range of leaves - HeaderStartSize = HeaderPrefix + "start_size" - HeaderEndSize = HeaderPrefix + "end_size" - - // Tree head - HeaderTimestamp = HeaderPrefix + "timestamp" - HeaderTreeSize = HeaderPrefix + "tree_size" - HeaderRootHash = HeaderPrefix + "root_hash" - - // Signature and signer identity - HeaderSignature = HeaderPrefix + "signature" - HeaderKeyHash = HeaderPrefix + "key_hash" -) - -// ToHTTP returns a signed tree-head as HTTP key-value pairs -func (sth *SignedTreeHead) ToHTTP() ([]byte, error) { - hdr := http.Header{} - hdr.Add(HeaderTimestamp, strconv.FormatUint(sth.Timestamp, 10)) - hdr.Add(HeaderTreeSize, strconv.FormatUint(sth.TreeSize, 10)) - hdr.Add(HeaderRootHash, hex.EncodeToString(sth.RootHash[:])) - for _, sigident := range sth.SigIdent { - hdr.Add(HeaderSignature, hex.EncodeToString(sigident.Signature[:])) - hdr.Add(HeaderKeyHash, hex.EncodeToString(sigident.KeyHash[:])) - } - return headerToBytes(hdr) -} - -// ToHTTP returns a consistency proof as HTTP key-value pairs -func (p *ConsistencyProof) ToHTTP() ([]byte, error) { - hdr := http.Header{} - hdr.Add(HeaderNewSize, strconv.FormatUint(p.NewSize, 10)) - hdr.Add(HeaderOldSize, strconv.FormatUint(p.OldSize, 10)) - for _, hash := range p.Path { - hdr.Add(HeaderConsistencyPath, hex.EncodeToString(hash[:])) - } - return headerToBytes(hdr) -} - -// ToHTTP returns an inclusion proof as HTTP key-value pairs -func (p *InclusionProof) ToHTTP() ([]byte, error) { - hdr := http.Header{} - hdr.Add(HeaderTreeSize, strconv.FormatUint(p.TreeSize, 10)) - hdr.Add(HeaderLeafIndex, strconv.FormatUint(p.LeafIndex, 10)) - for _, hash := range p.Path { - hdr.Add(HeaderInclusionPath, hex.EncodeToString(hash[:])) - } - return headerToBytes(hdr) -} - -// ToHTTP returns a leaf as HTTP key-value pairs -func (l *Leaf) ToHTTP() ([]byte, error) { - hdr := http.Header{} - hdr.Add(HeaderShardHint, strconv.FormatUint(l.ShardHint, 10)) - hdr.Add(HeaderChecksum, hex.EncodeToString(l.Checksum[:])) - hdr.Add(HeaderSignature, hex.EncodeToString(l.Signature[:])) - hdr.Add(HeaderKeyHash, hex.EncodeToString(l.KeyHash[:])) - return headerToBytes(hdr) -} - -// SignedTreeHeadFromHTTP parses a signed tree head from HTTP key-value pairs -func SignedTreeHeadFromHTTP(buf []byte) (*SignedTreeHead, error) { - hdr, err := headerFromBuf(buf) - if err != nil { - return nil, fmt.Errorf("headerFromBuf(): %v", err) - } - - // TreeHead - var sth SignedTreeHead - sth.Timestamp, err = strconv.ParseUint(hdr.Get(HeaderTimestamp), 10, 64) - if err != nil { - return nil, fmt.Errorf("invalid timestamp: %v", err) - } - sth.TreeSize, err = strconv.ParseUint(hdr.Get(HeaderTreeSize), 10, 64) - if err != nil { - return nil, fmt.Errorf("invalid tree size: %v", err) - } - if err := decodeHex(hdr.Get(HeaderRootHash), sth.RootHash[:]); err != nil { - return nil, fmt.Errorf("decodeHex(): %v", err) - } - - // SigIdent - signatures := hdr.Values(HeaderSignature) - keyHashes := hdr.Values(HeaderKeyHash) - if len(signatures) == 0 { - return nil, fmt.Errorf("no signer") - } - if len(signatures) != len(keyHashes) { - return nil, fmt.Errorf("mismatched signature-signer count") - } - for i := 0; i < len(signatures); i++ { - var sigident SigIdent - if err := decodeHex(signatures[i], sigident.Signature[:]); err != nil { - return nil, fmt.Errorf("decodeHex(): %v", err) - } - if err := decodeHex(keyHashes[i], sigident.KeyHash[:]); err != nil { - return nil, fmt.Errorf("decodeHex(): %v", err) - } - sth.SigIdent = append(sth.SigIdent, sigident) - } - return &sth, nil -} - -// ConsistencyProofFromHTTP parses a consistency proof from HTTP key-value pairs -func ConsistencyProofFromHTTP(buf []byte) (*ConsistencyProof, error) { - return nil, nil // TODO -} - -// InclusionProofFromHTTP parses an inclusion proof from HTTP key-value pairs -func InclusionProofFromHTTP(buf []byte) (*InclusionProof, error) { - return nil, nil // TODO -} - -// LeavesFromHTTP parses a list of leaves from HTTP key-value pairs -func LeavesFromHTTP(buf []byte) ([]*Leaf, error) { - return nil, nil // TODO -} - -// headerFromBuf parses ST log HTTP header key-value pairs from a response body -func headerFromBuf(buf []byte) (http.Header, error) { - hdr := http.Header{} - lines := strings.Split(string(buf), "\r\n") - lines = lines[:len(lines)-1] // skip the final empty line - for _, line := range lines { - split := strings.Split(line, ":") - if len(split) != 2 { - return nil, fmt.Errorf("invalid ST log HTTP header: %s", line) - } - if !strings.HasPrefix(strings.ToLower(split[0]), HeaderPrefix) { - return nil, fmt.Errorf("invalid ST log HTTP header prefix: %s", line) - } - hdr.Add(split[0], strings.TrimSpace(split[1])) - } - return hdr, nil -} - -// decodeHex decodes a hex-encoded string into a fixed-size output slice -func decodeHex(str string, out []byte) error { - buf, err := hex.DecodeString(str) - if err != nil { - return fmt.Errorf("hex.DecodeString(): %v", err) - } - if len(buf) != len(out) { - return fmt.Errorf("invalid length: %v", len(buf)) - } - copy(out, buf) - return nil -} - -// headerToBytes encodes a header as HTTP key-value pairs -func headerToBytes(hdr http.Header) ([]byte, error) { - buf := bytes.NewBuffer(nil) - if err := hdr.Write(buf); err != nil { - return nil, fmt.Errorf("hdr.Write(): %v", err) // should not happen - } - return buf.Bytes(), nil -} diff --git a/types/http_test.go b/types/http_test.go deleted file mode 100644 index 527bcdf..0000000 --- a/types/http_test.go +++ /dev/null @@ -1,331 +0,0 @@ -package types - -import ( - "bytes" - "encoding/hex" - "net/http" - "reflect" - "strings" - "testing" -) - -var ( - testZeroBuffer32 = [32]byte{} - testZeroBuffer64 = [64]byte{} -) - -func TestSignedTreeHeadToHTTP(t *testing.T) { - description := "valid: cosigned tree head with two signatures" - sth := &SignedTreeHead{ - TreeHead: TreeHead{ - Timestamp: 0, - TreeSize: 0, - RootHash: testBuffer32, - }, - SigIdent: []SigIdent{ - SigIdent{ - Signature: testZeroBuffer64, - KeyHash: testZeroBuffer32, - }, - SigIdent{ - Signature: testBuffer64, - KeyHash: testBuffer32, - }, - }, - } - want := map[string][]string{ - HeaderTimestamp: []string{"0"}, - HeaderTreeSize: []string{"0"}, - HeaderRootHash: []string{hex.EncodeToString(testBuffer32[:])}, - HeaderSignature: []string{ - hex.EncodeToString(testZeroBuffer64[:]), - hex.EncodeToString(testBuffer64[:]), - }, - HeaderKeyHash: []string{ - hex.EncodeToString(testZeroBuffer32[:]), - hex.EncodeToString(testBuffer32[:]), - }, - } - buf, err := sth.ToHTTP() - if err != nil { - t.Fatalf("sth.ToHTTP: %v", err) - } - hdr, err := headerFromBuf(buf) - if err != nil { - t.Fatalf("headerFromBuf: %v", err) - } - compareHeaderWithMap(t, description, hdr, want) -} - -func TestConsistencyProofToHTTP(t *testing.T) { // TODO -} - -func TestInclusionProofToHTTP(t *testing.T) { // TODO -} - -func TestLeafToHTTP(t *testing.T) { // TODO -} - -func TestSignedTreeHeadFromHTTP(t *testing.T) { - for _, table := range []struct { - description string - buf []byte - wantErr bool - wantSth *SignedTreeHead - }{ - { - description: "invalid: not ST log HTTP header", - buf: newHeaderBuf(t, map[string][]string{ - "user-agent": []string{"secret"}, - }), - wantErr: true, - }, - { - description: "invalid: timestamp", - buf: newHeaderBuf(t, map[string][]string{ - HeaderTreeSize: []string{"0"}, - HeaderRootHash: []string{hex.EncodeToString(testBuffer32[:])}, - HeaderSignature: []string{hex.EncodeToString(testBuffer64[:])}, - HeaderKeyHash: []string{hex.EncodeToString(testBuffer32[:])}, - }), - wantErr: true, - }, - { - description: "invalid: tree size", - buf: newHeaderBuf(t, map[string][]string{ - HeaderTimestamp: []string{"0"}, - HeaderRootHash: []string{hex.EncodeToString(testBuffer32[:])}, - HeaderSignature: []string{hex.EncodeToString(testBuffer64[:])}, - HeaderKeyHash: []string{hex.EncodeToString(testBuffer32[:])}, - }), - wantErr: true, - }, - { - description: "invalid: root hash", - buf: newHeaderBuf(t, map[string][]string{ - HeaderTimestamp: []string{"0"}, - HeaderTreeSize: []string{"0"}, - HeaderSignature: []string{hex.EncodeToString(testBuffer64[:])}, - HeaderKeyHash: []string{hex.EncodeToString(testBuffer32[:])}, - }), - wantErr: true, - }, - { - description: "invalid: signature", - buf: newHeaderBuf(t, map[string][]string{ - HeaderTimestamp: []string{"0"}, - HeaderTreeSize: []string{"0"}, - HeaderRootHash: []string{hex.EncodeToString(testBuffer32[:])}, - HeaderSignature: []string{hex.EncodeToString(testBuffer32[:])}, - HeaderKeyHash: []string{hex.EncodeToString(testBuffer32[:])}, - }), - wantErr: true, - }, - { - description: "invalid: key hash", - buf: newHeaderBuf(t, map[string][]string{ - HeaderTimestamp: []string{"0"}, - HeaderTreeSize: []string{"0"}, - HeaderRootHash: []string{hex.EncodeToString(testBuffer32[:])}, - HeaderSignature: []string{hex.EncodeToString(testBuffer64[:])}, - HeaderKeyHash: []string{hex.EncodeToString(testBuffer64[:])}, - }), - wantErr: true, - }, - { - description: "invalid: sigident count", - buf: newHeaderBuf(t, map[string][]string{ - HeaderTimestamp: []string{"0"}, - HeaderTreeSize: []string{"0"}, - HeaderRootHash: []string{hex.EncodeToString(testBuffer32[:])}, - HeaderSignature: []string{hex.EncodeToString(testBuffer64[:])}, - HeaderKeyHash: []string{ - hex.EncodeToString(testZeroBuffer32[:]), - hex.EncodeToString(testBuffer32[:]), - }, - }), - wantErr: true, - }, - { - description: "invalid: no signer", - buf: newHeaderBuf(t, map[string][]string{ - HeaderTimestamp: []string{"0"}, - HeaderTreeSize: []string{"0"}, - HeaderRootHash: []string{hex.EncodeToString(testBuffer32[:])}, - }), - wantErr: true, - }, - { - description: "valid: cosigned tree head with two signatures", - buf: newHeaderBuf(t, map[string][]string{ - HeaderTimestamp: []string{"0"}, - HeaderTreeSize: []string{"0"}, - HeaderRootHash: []string{hex.EncodeToString(testBuffer32[:])}, - HeaderSignature: []string{ - hex.EncodeToString(testZeroBuffer64[:]), - hex.EncodeToString(testBuffer64[:]), - }, - HeaderKeyHash: []string{ - hex.EncodeToString(testZeroBuffer32[:]), - hex.EncodeToString(testBuffer32[:]), - }, - }), - wantSth: &SignedTreeHead{ - TreeHead: TreeHead{ - Timestamp: 0, - TreeSize: 0, - RootHash: testBuffer32, - }, - SigIdent: []SigIdent{ - SigIdent{ - Signature: testZeroBuffer64, - KeyHash: testZeroBuffer32, - }, - SigIdent{ - Signature: testBuffer64, - KeyHash: testBuffer32, - }, - }, - }, - }, - } { - sth, err := SignedTreeHeadFromHTTP(table.buf) - if got, want := err != nil, table.wantErr; got != want { - t.Errorf("got error %v but wanted %v in test %q: %v", got, want, table.description, err) - } - if err != nil { - continue // nothing more to check on error - } - if got, want := sth, table.wantSth; !reflect.DeepEqual(got, want) { - t.Errorf("got signed tree head\n\t%v\nbut wanted\n\t%v\nin test %q", got, want, table.description) - } - } -} - -func TestHeaderFromBuf(t *testing.T) { - for _, table := range []struct { - description string - buf []byte - wantErr bool - wantMap map[string][]string - }{ - { - description: "invalid: split", - buf: []byte(HeaderPrefix + "k1: v1:v2\r\n"), - wantErr: true, - }, - { - description: "invalid: prefix", - buf: []byte("user-agent: secret\r\n"), - wantErr: true, - }, - { - description: "valid: one key with funky case", - buf: []byte(funkyCase(t, HeaderPrefix) + "k1: v1\r\n"), - wantMap: map[string][]string{ - HeaderPrefix + "k1": []string{"v1"}, - }, - }, - { - description: "valid: two keys where one has multiple values", - buf: []byte( - HeaderPrefix + "k1: v1 \r\n" + - HeaderPrefix + "k2: v2\r\n" + - HeaderPrefix + "k2: v3\r\n", - ), - wantMap: map[string][]string{ - HeaderPrefix + "k1": []string{"v1"}, - HeaderPrefix + "k2": []string{"v2", "v3"}, - }, - }, - } { - hdr, err := headerFromBuf(table.buf) - if got, want := err != nil, table.wantErr; got != want { - t.Errorf("got error %v but wanted %v in test %q: %v", got, want, table.description, err) - } - if err != nil { - continue // nothing more to check on error - } - compareHeaderWithMap(t, table.description, hdr, table.wantMap) - } -} - -func TestDecodeHex(t *testing.T) { - for _, table := range []struct { - description string - hex string - wantErr bool - wantBuf [4]byte - }{ - { - description: "invalid: too short input", - hex: "000102", - wantErr: true, - }, - { - description: "invalid: too large input", - hex: "0001020304", - wantErr: true, - }, - { - description: "invalid: not hex (1/2)", - hex: "000102030", - wantErr: true, - }, - { - description: "invalid: not hex (2/2)", - hex: "0001020q", - wantErr: true, - }, - { - description: "valid", - hex: "00010203", - wantBuf: [4]byte{0, 1, 2, 3}, - }, - } { - var buf [4]byte - err := decodeHex(table.hex, buf[:]) - if got, want := err != nil, table.wantErr; got != want { - t.Errorf("got error %v but wanted %v in test %q: %v", got, want, table.description, err) - } - if err != nil { - continue // nothing more to check on error - } - if got, want := buf[:], table.wantBuf[:]; !bytes.Equal(got, want) { - t.Errorf("got buf %v but wanted %v in test %q", got, want, table.description) - } - } -} - -func newHeaderBuf(t *testing.T, kv map[string][]string) []byte { - t.Helper() - hdr := http.Header{} - for key, values := range kv { - for _, value := range values { - hdr.Add(key, value) - } - } - buf := bytes.NewBuffer(nil) - if err := hdr.Write(buf); err != nil { - t.Fatalf("hdr.Write(): %v", err) - } - return buf.Bytes() -} - -func compareHeaderWithMap(t *testing.T, description string, hdr http.Header, wantMap map[string][]string) { - t.Helper() - if got, want := len(hdr), len(wantMap); got != want { - t.Errorf("got %d keys but wanted %d in test %q", got, want, description) - } - for key, value := range wantMap { - if got, want := hdr.Values(key), value; !reflect.DeepEqual(got, want) { - t.Errorf("got value %v but wanted %v for key %v in test %q", got, want, key, description) - } - } -} - -func funkyCase(t *testing.T, str string) string { - t.Helper() - splitIndex := len(str) / 2 - return strings.ToLower(str[:splitIndex]) + strings.ToUpper(str[splitIndex:]) -} diff --git a/types/trunnel.go b/types/trunnel.go index 72ae68d..268f6f7 100644 --- a/types/trunnel.go +++ b/types/trunnel.go @@ -46,12 +46,15 @@ func (l *Leaf) Unmarshal(buf []byte) error { l.ShardHint = binary.BigEndian.Uint64(buf) offset := 8 // Checksum + l.Checksum = &[HashSize]byte{} copy(l.Checksum[:], buf[offset:offset+HashSize]) offset += HashSize // Signature + l.Signature = &[SignatureSize]byte{} copy(l.Signature[:], buf[offset:offset+SignatureSize]) offset += SignatureSize // KeyHash + l.KeyHash = &[HashSize]byte{} copy(l.KeyHash[:], buf[offset:]) return nil } diff --git a/types/trunnel_test.go b/types/trunnel_test.go index 0fa7656..297578c 100644 --- a/types/trunnel_test.go +++ b/types/trunnel_test.go @@ -7,8 +7,8 @@ import ( ) var ( - testBuffer32 = [32]byte{0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31} - testBuffer64 = [64]byte{0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, 62, 63} + testBuffer32 = &[32]byte{0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31} + testBuffer64 = &[64]byte{0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, 62, 63} ) func TestMarshalMessage(t *testing.T) { @@ -105,7 +105,7 @@ func TestUnmarshalLeaf(t *testing.T) { t.Errorf("got error %v but wanted %v in test %q: %v", got, want, table.description, err) } if err != nil { - continue // nothing more to check on error + continue } if got, want := &leaf, table.want; !reflect.DeepEqual(got, want) { t.Errorf("got leaf\n\t%v\nbut wanted\n\t%v\nin test %q\n", got, want, table.description) diff --git a/types/types.go b/types/types.go index 483dac0..2da40da 100644 --- a/types/types.go +++ b/types/types.go @@ -6,8 +6,9 @@ import ( ) const ( - HashSize = sha256.Size - SignatureSize = ed25519.SignatureSize + HashSize = sha256.Size + SignatureSize = ed25519.SignatureSize + VerificationKeySize = ed25519.PublicKeySize ) // Leaf is the log's Merkle tree leaf. @@ -22,14 +23,14 @@ type Leaf struct { // these values to fit the log's shard interval and the opaque data in question. type Message struct { ShardHint uint64 - Checksum [HashSize]byte + Checksum *[HashSize]byte } // SigIdent is composed of a signature-signer pair. The signature is computed // over the Trunnel-serialized leaf message. KeyHash identifies the signer. type SigIdent struct { - Signature [SignatureSize]byte - KeyHash [HashSize]byte + Signature *[SignatureSize]byte + KeyHash *[HashSize]byte } // SignedTreeHead is composed of a tree head and a list of signature-signer @@ -40,7 +41,7 @@ type SigIdent struct { // Ref: https://github.com/system-transparency/stfe/blob/design/doc/api.md#get-tree-head-latest type SignedTreeHead struct { TreeHead - SigIdent []SigIdent + SigIdent []*SigIdent } // TreeHead is the log's tree head. @@ -49,7 +50,7 @@ type SignedTreeHead struct { type TreeHead struct { Timestamp uint64 TreeSize uint64 - RootHash [HashSize]byte + RootHash *[HashSize]byte } // ConsistencyProof is a consistency proof that proves the log's append-only @@ -59,7 +60,7 @@ type TreeHead struct { type ConsistencyProof struct { NewSize uint64 OldSize uint64 - Path [][HashSize]byte + Path []*[HashSize]byte } // InclusionProof is an inclusion proof that proves a leaf is included in the @@ -69,5 +70,49 @@ type ConsistencyProof struct { type InclusionProof struct { TreeSize uint64 LeafIndex uint64 - Path [][HashSize]byte + Path []*[HashSize]byte +} + +// LeafList is a list of leaves +type LeafList []*Leaf + +// ConsistencyProofRequest is a get-consistency-proof request +// +// Ref: https://github.com/system-transparency/stfe/blob/design/doc/api.md#get-consistency-proof +type ConsistencyProofRequest struct { + NewSize uint64 + OldSize uint64 +} + +// InclusionProofRequest is a get-proof-by-hash request +// +// Ref: https://github.com/system-transparency/stfe/blob/design/doc/api.md#get-proof-by-hash +type InclusionProofRequest struct { + LeafHash *[HashSize]byte + TreeSize uint64 +} + +// LeavesRequest is a get-leaves request +// +// Ref: https://github.com/system-transparency/stfe/blob/design/doc/api.md#get-leaves +type LeavesRequest struct { + StartSize uint64 + EndSize uint64 +} + +// LeafRequest is an add-leaf request +// +// Ref: https://github.com/system-transparency/stfe/blob/design/doc/api.md#add-leaf +type LeafRequest struct { + Message + Signature *[SignatureSize]byte + VerificationKey *[VerificationKeySize]byte + DomainHint string +} + +// CosignatureRequest is an add-cosignature request +// +// Ref: https://github.com/system-transparency/stfe/blob/design/doc/api.md#add-cosignature +type CosignatureRequest struct { + SigIdent } -- cgit v1.2.3 From 1ac7f1bad7596bc0cc489d85de8bdf5d195b99a3 Mon Sep 17 00:00:00 2001 From: Rasmus Dahlberg Date: Mon, 31 May 2021 23:56:49 +0200 Subject: added types and fixed an error - The wrong signature header was used for leaves - Added SigIdent marshalling - Added a wrapper that uses the log's hash function --- types/ascii.go | 21 ++++++++++++++------- types/ascii_test.go | 6 +++--- types/util.go | 13 +++++++++++++ 3 files changed, 30 insertions(+), 10 deletions(-) create mode 100644 types/util.go diff --git a/types/ascii.go b/types/ascii.go index 88b372e..565e52d 100644 --- a/types/ascii.go +++ b/types/ascii.go @@ -200,7 +200,7 @@ func (l *Leaf) MarshalASCII(w io.Writer) error { if err := writeASCII(w, Checksum, hex.EncodeToString(l.Checksum[:])); err != nil { return fmt.Errorf("writeASCII: %v", err) } - if err := writeASCII(w, Signature, hex.EncodeToString(l.Signature[:])); err != nil { + if err := writeASCII(w, SignatureOverMessage, hex.EncodeToString(l.Signature[:])); err != nil { return fmt.Errorf("writeASCII: %v", err) } if err := writeASCII(w, KeyHash, hex.EncodeToString(l.KeyHash[:])); err != nil { @@ -220,16 +220,23 @@ func (sth *SignedTreeHead) MarshalASCII(w io.Writer) error { return fmt.Errorf("writeASCII: %v", err) } for _, sigident := range sth.SigIdent { - if err := writeASCII(w, Signature, hex.EncodeToString(sigident.Signature[:])); err != nil { - return fmt.Errorf("writeASCII: %v", err) - } - if err := writeASCII(w, KeyHash, hex.EncodeToString(sigident.KeyHash[:])); err != nil { - return fmt.Errorf("writeASCII: %v", err) + if err := sigident.MarshalASCII(w); err != nil { + return fmt.Errorf("MarshalASCII: %v", err) } } return nil } +func (si *SigIdent) MarshalASCII(w io.Writer) error { + if err := writeASCII(w, Signature, hex.EncodeToString(si.Signature[:])); err != nil { + return fmt.Errorf("writeASCII: %v", err) + } + if err := writeASCII(w, KeyHash, hex.EncodeToString(si.KeyHash[:])); err != nil { + return fmt.Errorf("writeASCII: %v", err) + } + return nil +} + func (p *ConsistencyProof) MarshalASCII(w io.Writer) error { if err := writeASCII(w, NewSize, strconv.FormatUint(p.NewSize, 10)); err != nil { return fmt.Errorf("writeASCII: %v", err) @@ -385,7 +392,7 @@ func (req *LeafRequest) UnmarshalASCII(r io.Reader) error { if req.Checksum, err = msg.GetHash(Checksum); err != nil { return fmt.Errorf("GetHash(Checksum): %v", err) } - if req.Signature, err = msg.GetSignature(Signature); err != nil { + if req.Signature, err = msg.GetSignature(SignatureOverMessage); err != nil { return fmt.Errorf("GetSignature: %v", err) } if req.VerificationKey, err = msg.GetVerificationKey(VerificationKey); err != nil { diff --git a/types/ascii_test.go b/types/ascii_test.go index 74a1e37..92732f9 100644 --- a/types/ascii_test.go +++ b/types/ascii_test.go @@ -106,12 +106,12 @@ func TestLeafMarshalASCII(t *testing.T) { // Leaf 1 ShardHint, Delim, 123, EOL, Checksum, Delim, testBuffer32[:], EOL, - Signature, Delim, testBuffer64[:], EOL, + SignatureOverMessage, Delim, testBuffer64[:], EOL, KeyHash, Delim, testBuffer32[:], EOL, // Leaf 2 ShardHint, Delim, 456, EOL, Checksum, Delim, testBuffer32[:], EOL, - Signature, Delim, testBuffer64[:], EOL, + SignatureOverMessage, Delim, testBuffer64[:], EOL, KeyHash, Delim, testBuffer32[:], EOL, )) buf := bytes.NewBuffer(nil) @@ -399,7 +399,7 @@ func TestLeafRequestUnmarshalASCII(t *testing.T) { "%s%s%d%s"+"%s%s%x%s"+"%s%s%x%s"+"%s%s%x%s"+"%s%s%s%s", ShardHint, Delim, 123, EOL, Checksum, Delim, testBuffer32[:], EOL, - Signature, Delim, testBuffer64[:], EOL, + SignatureOverMessage, Delim, testBuffer64[:], EOL, VerificationKey, Delim, testBuffer32[:], EOL, DomainHint, Delim, "example.com", EOL, )), diff --git a/types/util.go b/types/util.go new file mode 100644 index 0000000..dc8ccba --- /dev/null +++ b/types/util.go @@ -0,0 +1,13 @@ +package types + +import ( + "crypto/sha256" +) + +func Hash(buf []byte) *[HashSize]byte { + var ret [HashSize]byte + hash := sha256.New() + hash.Write(buf) + copy(ret[:], hash.Sum(nil)) + return &ret +} -- cgit v1.2.3 From 519784b5ee58370d6c1262b0eb6c72ee3580f293 Mon Sep 17 00:00:00 2001 From: Rasmus Dahlberg Date: Tue, 1 Jun 2021 00:21:30 +0200 Subject: started to update stfe server Work in progress. --- endpoint.go | 174 +++++++++++++++++++++++++++--------------------------- instance.go | 13 ++-- log_parameters.go | 80 ++++++++----------------- request.go | 160 +++++++++++++++++++++---------------------------- server/main.go | 90 +++++++++++++--------------- sth.go | 144 +++++++++++++++++++++----------------------- trillian.go | 12 ++-- util.go | 48 +++++++-------- 8 files changed, 326 insertions(+), 395 deletions(-) diff --git a/endpoint.go b/endpoint.go index d3da95e..98a5ce8 100644 --- a/endpoint.go +++ b/endpoint.go @@ -2,10 +2,10 @@ package stfe import ( "context" + "crypto/ed25519" "fmt" - "strings" - "net/http" + "strings" "github.com/golang/glog" "github.com/google/trillian" @@ -16,14 +16,14 @@ import ( type Endpoint string const ( - EndpointAddEntry = Endpoint("add-entry") + EndpointAddEntry = Endpoint("add-leaf") EndpointAddCosignature = Endpoint("add-cosignature") - EndpointGetLatestSth = Endpoint("get-latest-sth") - EndpointGetStableSth = Endpoint("get-stable-sth") - EndpointGetCosignedSth = Endpoint("get-cosigned-sth") + EndpointGetLatestSth = Endpoint("get-tree-head-latest") + EndpointGetStableSth = Endpoint("get-tree-head-to-sign") + EndpointGetCosignedSth = Endpoint("get-tree-head-cosigned") EndpointGetProofByHash = Endpoint("get-proof-by-hash") EndpointGetConsistencyProof = Endpoint("get-consistency-proof") - EndpointGetEntries = Endpoint("get-entries") + EndpointGetEntries = Endpoint("get-leaves") ) // Path joins a number of components to form a full endpoint path, e.g., base @@ -34,18 +34,14 @@ func (e Endpoint) Path(components ...string) string { func addEntry(ctx context.Context, i *Instance, w http.ResponseWriter, r *http.Request) (int, error) { glog.V(3).Info("handling add-entry request") - item, err := i.LogParameters.parseAddEntryV1Request(r) + leaf, err := i.LogParameters.parseAddEntryV1Request(r) if err != nil { return http.StatusBadRequest, fmt.Errorf("parseAddEntryV1Request: %v", err) } - leaf, err := types.Marshal(*item) - if err != nil { - return http.StatusInternalServerError, fmt.Errorf("Marshal: %v", err) // should never happen - } trsp, err := i.Client.QueueLeaf(ctx, &trillian.QueueLeafRequest{ LogId: i.LogParameters.TreeId, Leaf: &trillian.LogLeaf{ - LeafValue: leaf, + LeafValue: leaf.Marshal(), ExtraData: nil, }, }) @@ -57,12 +53,13 @@ func addEntry(ctx context.Context, i *Instance, w http.ResponseWriter, r *http.R func addCosignature(ctx context.Context, i *Instance, w http.ResponseWriter, r *http.Request) (int, error) { glog.V(3).Info("handling add-cosignature request") - costh, err := i.LogParameters.parseAddCosignatureV1Request(r) + req, err := i.LogParameters.parseAddCosignatureRequest(r) if err != nil { - return http.StatusBadRequest, err + return http.StatusBadRequest, fmt.Errorf("parseAddCosignatureRequest: %v", err) } - if err := i.SthSource.AddCosignature(ctx, costh); err != nil { - return http.StatusBadRequest, err + vk := i.LogParameters.Witnesses[*req.KeyHash] + if err := i.SthSource.AddCosignature(ctx, ed25519.PublicKey(vk[:]), req.Signature); err != nil { + return http.StatusBadRequest, fmt.Errorf("AddCosignature: %v", err) } return http.StatusOK, nil } @@ -73,8 +70,8 @@ func getLatestSth(ctx context.Context, i *Instance, w http.ResponseWriter, _ *ht if err != nil { return http.StatusInternalServerError, fmt.Errorf("Latest: %v", err) } - if err := writeOctetResponse(w, *sth); err != nil { - return http.StatusInternalServerError, fmt.Errorf("writeOctetResponse: %v", err) + if err := sth.MarshalASCII(w); err != nil { + return http.StatusInternalServerError, fmt.Errorf("MarshalASCII: %v", err) } return http.StatusOK, nil } @@ -85,101 +82,106 @@ func getStableSth(ctx context.Context, i *Instance, w http.ResponseWriter, _ *ht if err != nil { return http.StatusInternalServerError, fmt.Errorf("Latest: %v", err) } - if err := writeOctetResponse(w, *sth); err != nil { - return http.StatusInternalServerError, fmt.Errorf("writeOctetResponse: %v", err) + if err := sth.MarshalASCII(w); err != nil { + return http.StatusInternalServerError, fmt.Errorf("MarshalASCII: %v", err) } return http.StatusOK, nil } func getCosignedSth(ctx context.Context, i *Instance, w http.ResponseWriter, _ *http.Request) (int, error) { glog.V(3).Info("handling get-cosigned-sth request") - costh, err := i.SthSource.Cosigned(ctx) + sth, err := i.SthSource.Cosigned(ctx) if err != nil { return http.StatusInternalServerError, fmt.Errorf("Cosigned: %v", err) } - if err := writeOctetResponse(w, *costh); err != nil { - return http.StatusInternalServerError, fmt.Errorf("writeOctetResponse: %v", err) + if err := sth.MarshalASCII(w); err != nil { + return http.StatusInternalServerError, fmt.Errorf("MarshalASCII: %v", err) } return http.StatusOK, nil } func getConsistencyProof(ctx context.Context, i *Instance, w http.ResponseWriter, r *http.Request) (int, error) { glog.V(3).Info("handling get-consistency-proof request") - req, err := i.LogParameters.parseGetConsistencyProofV1Request(r) + req, err := i.LogParameters.parseGetConsistencyProofRequest(r) if err != nil { return http.StatusBadRequest, err } trsp, err := i.Client.GetConsistencyProof(ctx, &trillian.GetConsistencyProofRequest{ LogId: i.LogParameters.TreeId, - FirstTreeSize: int64(req.First), - SecondTreeSize: int64(req.Second), + FirstTreeSize: int64(req.OldSize), + SecondTreeSize: int64(req.NewSize), }) if errInner := checkGetConsistencyProof(i.LogParameters, trsp, err); errInner != nil { return http.StatusInternalServerError, fmt.Errorf("bad GetConsistencyProofResponse: %v", errInner) } - if err := writeOctetResponse(w, *types.NewConsistencyProofV1(i.LogParameters.LogId, req.First, req.Second, NewNodePathFromHashPath(trsp.Proof.Hashes))); err != nil { - return http.StatusInternalServerError, fmt.Errorf("writeOctetResponse: %v", err) - } - return http.StatusOK, nil -} - -func getProofByHash(ctx context.Context, i *Instance, w http.ResponseWriter, r *http.Request) (int, error) { - glog.V(3).Info("handling get-proof-by-hash request") - req, err := i.LogParameters.parseGetProofByHashV1Request(r) - if err != nil { - return http.StatusBadRequest, err - } - - trsp, err := i.Client.GetInclusionProofByHash(ctx, &trillian.GetInclusionProofByHashRequest{ - LogId: i.LogParameters.TreeId, - LeafHash: req.Hash[:], - TreeSize: int64(req.TreeSize), - OrderBySequence: true, - }) - if errInner := checkGetInclusionProofByHash(i.LogParameters, trsp, err); errInner != nil { - return http.StatusInternalServerError, fmt.Errorf("bad GetInclusionProofByHashResponse: %v", errInner) - } - - if err := writeOctetResponse(w, *types.NewInclusionProofV1(i.LogParameters.LogId, req.TreeSize, uint64(trsp.Proof[0].LeafIndex), NewNodePathFromHashPath(trsp.Proof[0].Hashes))); err != nil { - return http.StatusInternalServerError, fmt.Errorf("writeOctetResponse: %v", err) - } - return http.StatusOK, nil -} - -func getEntries(ctx context.Context, i *Instance, w http.ResponseWriter, r *http.Request) (int, error) { - glog.V(3).Info("handling get-entries request") - req, err := i.LogParameters.parseGetEntriesV1Request(r) - if err != nil { - return http.StatusBadRequest, err - } - - trsp, err := i.Client.GetLeavesByRange(ctx, &trillian.GetLeavesByRangeRequest{ - LogId: i.LogParameters.TreeId, - StartIndex: int64(req.Start), - Count: int64(req.End-req.Start) + 1, - }) - if errInner := checkGetLeavesByRange(req, trsp, err); errInner != nil { - return http.StatusInternalServerError, fmt.Errorf("checkGetLeavesByRangeResponse: %v", errInner) // there is one StatusBadRequest in here tho.. + proof := &types.ConsistencyProof{ + NewSize: req.NewSize, + OldSize: req.OldSize, + Path: NodePathFromHashes(trsp.Proof.Hashes), } - - if rsp, err := NewStItemListFromLeaves(trsp.Leaves); err != nil { - return http.StatusInternalServerError, fmt.Errorf("NewStItemListFromLeaves: %v", err) // should never happen - } else if err := writeOctetResponse(w, *rsp); err != nil { - return http.StatusInternalServerError, fmt.Errorf("writeOctetResponse: %v", err) + if err := proof.MarshalASCII(w); err != nil { + return http.StatusInternalServerError, fmt.Errorf("MarshalASCII: %v", err) } return http.StatusOK, nil } -func writeOctetResponse(w http.ResponseWriter, i interface{}) error { - b, err := types.Marshal(i) - if err != nil { - return fmt.Errorf("Marshal: %v", err) - } - w.Header().Set("Content-Type", "application/octet-stream") - if _, err := w.Write(b); err != nil { - return fmt.Errorf("Write: %v", err) - } - return nil -} +//func getProofByHash(ctx context.Context, i *Instance, w http.ResponseWriter, r *http.Request) (int, error) { +// glog.V(3).Info("handling get-proof-by-hash request") +// req, err := i.LogParameters.parseGetProofByHashV1Request(r) +// if err != nil { +// return http.StatusBadRequest, err +// } +// +// trsp, err := i.Client.GetInclusionProofByHash(ctx, &trillian.GetInclusionProofByHashRequest{ +// LogId: i.LogParameters.TreeId, +// LeafHash: req.Hash[:], +// TreeSize: int64(req.TreeSize), +// OrderBySequence: true, +// }) +// if errInner := checkGetInclusionProofByHash(i.LogParameters, trsp, err); errInner != nil { +// return http.StatusInternalServerError, fmt.Errorf("bad GetInclusionProofByHashResponse: %v", errInner) +// } +// +// if err := writeOctetResponse(w, *types.NewInclusionProofV1(i.LogParameters.LogId, req.TreeSize, uint64(trsp.Proof[0].LeafIndex), NewNodePathFromHashPath(trsp.Proof[0].Hashes))); err != nil { +// return http.StatusInternalServerError, fmt.Errorf("writeOctetResponse: %v", err) +// } +// return http.StatusOK, nil +//} +// +//func getEntries(ctx context.Context, i *Instance, w http.ResponseWriter, r *http.Request) (int, error) { +// glog.V(3).Info("handling get-entries request") +// req, err := i.LogParameters.parseGetEntriesV1Request(r) +// if err != nil { +// return http.StatusBadRequest, err +// } +// +// trsp, err := i.Client.GetLeavesByRange(ctx, &trillian.GetLeavesByRangeRequest{ +// LogId: i.LogParameters.TreeId, +// StartIndex: int64(req.Start), +// Count: int64(req.End-req.Start) + 1, +// }) +// if errInner := checkGetLeavesByRange(req, trsp, err); errInner != nil { +// return http.StatusInternalServerError, fmt.Errorf("checkGetLeavesByRangeResponse: %v", errInner) // there is one StatusBadRequest in here tho.. +// } +// +// if rsp, err := NewStItemListFromLeaves(trsp.Leaves); err != nil { +// return http.StatusInternalServerError, fmt.Errorf("NewStItemListFromLeaves: %v", err) // should never happen +// } else if err := writeOctetResponse(w, *rsp); err != nil { +// return http.StatusInternalServerError, fmt.Errorf("writeOctetResponse: %v", err) +// } +// return http.StatusOK, nil +//} +// +//func writeOctetResponse(w http.ResponseWriter, i interface{}) error { +// b, err := types.Marshal(i) +// if err != nil { +// return fmt.Errorf("Marshal: %v", err) +// } +// w.Header().Set("Content-Type", "application/octet-stream") +// if _, err := w.Write(b); err != nil { +// return fmt.Errorf("Write: %v", err) +// } +// return nil +//} diff --git a/instance.go b/instance.go index 67336f8..d11032e 100644 --- a/instance.go +++ b/instance.go @@ -9,6 +9,7 @@ import ( "github.com/golang/glog" "github.com/google/trillian" + "github.com/system-transparency/stfe/types" ) // Instance is an instance of the system transparency front-end @@ -26,9 +27,9 @@ func (i *Instance) Handlers() []Handler { Handler{Instance: i, Handler: getLatestSth, Endpoint: EndpointGetLatestSth, Method: http.MethodGet}, Handler{Instance: i, Handler: getStableSth, Endpoint: EndpointGetStableSth, Method: http.MethodGet}, Handler{Instance: i, Handler: getCosignedSth, Endpoint: EndpointGetCosignedSth, Method: http.MethodGet}, - Handler{Instance: i, Handler: getProofByHash, Endpoint: EndpointGetProofByHash, Method: http.MethodPost}, + //Handler{Instance: i, Handler: getProofByHash, Endpoint: EndpointGetProofByHash, Method: http.MethodPost}, Handler{Instance: i, Handler: getConsistencyProof, Endpoint: EndpointGetConsistencyProof, Method: http.MethodPost}, - Handler{Instance: i, Handler: getEntries, Endpoint: EndpointGetEntries, Method: http.MethodPost}, + //Handler{Instance: i, Handler: getEntries, Endpoint: EndpointGetEntries, Method: http.MethodPost}, } } @@ -52,10 +53,10 @@ func (a Handler) ServeHTTP(w http.ResponseWriter, r *http.Request) { var now time.Time = time.Now() var statusCode int defer func() { - rspcnt.Inc(a.Instance.LogParameters.LogIdStr, string(a.Endpoint), fmt.Sprintf("%d", statusCode)) - latency.Observe(time.Now().Sub(now).Seconds(), a.Instance.LogParameters.LogIdStr, string(a.Endpoint), fmt.Sprintf("%d", statusCode)) + rspcnt.Inc(a.Instance.LogParameters.LogId, string(a.Endpoint), fmt.Sprintf("%d", statusCode)) + latency.Observe(time.Now().Sub(now).Seconds(), a.Instance.LogParameters.LogId, string(a.Endpoint), fmt.Sprintf("%d", statusCode)) }() - reqcnt.Inc(a.Instance.LogParameters.LogIdStr, string(a.Endpoint)) + reqcnt.Inc(a.Instance.LogParameters.LogId, string(a.Endpoint)) ctx, cancel := context.WithDeadline(r.Context(), now.Add(a.Instance.LogParameters.Deadline)) defer cancel() @@ -69,6 +70,6 @@ func (a Handler) ServeHTTP(w http.ResponseWriter, r *http.Request) { statusCode, err := a.Handler(ctx, a.Instance, w, r) if err != nil { glog.Warningf("handler error %s/%s: %v", a.Instance.LogParameters.Prefix, a.Endpoint, err) - http.Error(w, "", statusCode) + http.Error(w, fmt.Sprintf("%s%s%s%s", "Error", types.Delim, err.Error(), types.EOL), statusCode) } } diff --git a/log_parameters.go b/log_parameters.go index a2a2d7a..aceff3e 100644 --- a/log_parameters.go +++ b/log_parameters.go @@ -2,6 +2,7 @@ package stfe import ( "crypto" + "crypto/ed25519" "fmt" "time" @@ -10,66 +11,37 @@ import ( // LogParameters is a collection of log parameters type LogParameters struct { - LogId *types.Namespace // log identifier - LogIdBytes []byte // serialized log id - LogIdStr string // serialized log id (hex) - TreeId int64 // used internally by Trillian - Prefix string // e.g., "test" for /test - MaxRange int64 // max entries per get-entries request - SubmitterPolicy bool // if we have a submitter policy (true means that namespaces must be registered) - WitnessPolicy bool // if we have a witness policy (true means that namespaces must be registered) - Submitters *types.NamespacePool // trusted submitters - Witnesses *types.NamespacePool // trusted witnesses - Deadline time.Duration // gRPC deadline - Interval time.Duration // cosigning sth frequency - HashType crypto.Hash // hash function used by Trillian - Signer crypto.Signer // access to Ed25519 private key -} + LogId string // serialized log id (hex) + TreeId int64 // used internally by Trillian + Prefix string // e.g., "test" for /test + MaxRange int64 // max entries per get-entries request + Deadline time.Duration // gRPC deadline + Interval time.Duration // cosigning sth frequency + HashType crypto.Hash // hash function used by Trillian + Signer crypto.Signer // access to Ed25519 private key -// NewLogParameters creates newly initialized log parameters -func NewLogParameters(signer crypto.Signer, logId *types.Namespace, treeId int64, prefix string, submitters, witnesses *types.NamespacePool, maxRange int64, interval, deadline time.Duration, submitterPolicy, witnessPolicy bool) (*LogParameters, error) { - logIdBytes, err := types.Marshal(*logId) - if err != nil { - return nil, fmt.Errorf("Marshal failed for log identifier: %v", err) - } - return &LogParameters{ - LogId: logId, - LogIdBytes: logIdBytes, - LogIdStr: fmt.Sprintf("%x", logIdBytes), - TreeId: treeId, - Prefix: prefix, - MaxRange: maxRange, - SubmitterPolicy: submitterPolicy, - WitnessPolicy: witnessPolicy, - Submitters: submitters, - Witnesses: witnesses, - Deadline: deadline, - Interval: interval, - HashType: crypto.SHA256, - Signer: signer, - }, nil + // Witnesses map trusted witness identifiers to public verification keys + Witnesses map[[types.HashSize]byte][types.VerificationKeySize]byte } -// SignTreeHeadV1 signs a TreeHeadV1 structure -func (lp *LogParameters) SignTreeHeadV1(th *types.TreeHeadV1) (*types.StItem, error) { - serialized, err := types.Marshal(*th) - if err != nil { - return nil, fmt.Errorf("Marshal failed for TreeHeadV1: %v", err) - } - sig, err := lp.Signer.Sign(nil, serialized, crypto.Hash(0)) +// Sign signs a tree head +func (lp *LogParameters) Sign(th *types.TreeHead) (*types.SignedTreeHead, error) { + sig, err := lp.Signer.Sign(nil, th.Marshal(), crypto.Hash(0)) if err != nil { return nil, fmt.Errorf("Sign failed: %v", err) } - lastSthTimestamp.Set(float64(time.Now().Unix()), lp.LogIdStr) - lastSthSize.Set(float64(th.TreeSize), lp.LogIdStr) - return &types.StItem{ - Format: types.StFormatSignedTreeHeadV1, - SignedTreeHeadV1: &types.SignedTreeHeadV1{ - TreeHead: *th, - Signature: types.SignatureV1{ - Namespace: *lp.LogId, - Signature: sig, - }, + lastSthTimestamp.Set(float64(time.Now().Unix()), lp.LogId) + lastSthSize.Set(float64(th.TreeSize), lp.LogId) + + sigident := types.SigIdent{ + KeyHash: types.Hash(lp.Signer.Public().(ed25519.PublicKey)[:]), + Signature: &[types.SignatureSize]byte{}, + } + copy(sigident.Signature[:], sig) + return &types.SignedTreeHead{ + TreeHead: *th, + SigIdent: []*types.SigIdent{ + &sigident, }, }, nil } diff --git a/request.go b/request.go index 7c95f34..3b57779 100644 --- a/request.go +++ b/request.go @@ -3,114 +3,90 @@ package stfe import ( "fmt" - "io/ioutil" + "crypto/ed25519" "net/http" "github.com/system-transparency/stfe/types" ) -func (lp *LogParameters) parseAddEntryV1Request(r *http.Request) (*types.StItem, error) { - var item types.StItem - if err := unpackOctetPost(r, &item); err != nil { - return nil, fmt.Errorf("unpackOctetPost: %v", err) - } - if item.Format != types.StFormatSignedChecksumV1 { - return nil, fmt.Errorf("invalid StItem format: %v", item.Format) - } - - // Check that submitter namespace is valid - namespace := &item.SignedChecksumV1.Signature.Namespace - if lp.SubmitterPolicy { - var ok bool - if namespace, ok = lp.Submitters.Find(namespace); !ok { - return nil, fmt.Errorf("unknown submitter namespace: %v", namespace) - } - } - // Check that namespace signed add-entry request - if msg, err := types.Marshal(item.SignedChecksumV1.Data); err != nil { - return nil, fmt.Errorf("Marshal: %v", err) // should never happen - } else if err := namespace.Verify(msg, item.SignedChecksumV1.Signature.Signature); err != nil { - return nil, fmt.Errorf("Verify: %v", err) - } - return &item, nil -} - -func (lp *LogParameters) parseAddCosignatureV1Request(r *http.Request) (*types.StItem, error) { - var item types.StItem - if err := unpackOctetPost(r, &item); err != nil { - return nil, fmt.Errorf("unpackOctetPost: %v", err) - } - if item.Format != types.StFormatCosignedTreeHeadV1 { - return nil, fmt.Errorf("invalid StItem format: %v", item.Format) - } - if got, want := len(item.CosignedTreeHeadV1.Cosignatures), 1; got != want { - return nil, fmt.Errorf("invalid number of cosignatures: %d", got) - } - - // Check that witness namespace is valid - namespace := &item.CosignedTreeHeadV1.Cosignatures[0].Namespace - if lp.WitnessPolicy { - var ok bool - if namespace, ok = lp.Witnesses.Find(namespace); !ok { - return nil, fmt.Errorf("unknown witness namespace: %v", namespace) - } - } - // Check that namespace signed add-cosignature request - if msg, err := types.Marshal(*types.NewSignedTreeHeadV1(&item.CosignedTreeHeadV1.SignedTreeHead.TreeHead, &item.CosignedTreeHeadV1.SignedTreeHead.Signature).SignedTreeHeadV1); err != nil { - return nil, fmt.Errorf("Marshal: %v", err) // should never happen - } else if err := namespace.Verify(msg, item.CosignedTreeHeadV1.Cosignatures[0].Signature); err != nil { - return nil, fmt.Errorf("Verify: %v", err) +func (lp *LogParameters) parseAddEntryV1Request(r *http.Request) (*types.Leaf, error) { + var req types.LeafRequest + if err := req.UnmarshalASCII(r.Body); err != nil { + return nil, fmt.Errorf("UnmarshalASCII: %v", err) } - return &item, nil -} -func (lp *LogParameters) parseGetConsistencyProofV1Request(r *http.Request) (*types.GetConsistencyProofV1, error) { - var item types.GetConsistencyProofV1 - if err := unpackOctetPost(r, &item); err != nil { - return nil, fmt.Errorf("unpackOctetPost: %v", err) - } - if item.First < 1 { - return nil, fmt.Errorf("first(%d) must be larger than zero", item.First) - } - if item.Second <= item.First { - return nil, fmt.Errorf("second(%d) must be larger than first(%d)", item.Second, item.First) - } - return &item, nil + if pub, msg, sig := ed25519.PublicKey(req.VerificationKey[:]), req.Message.Marshal(), req.Signature[:]; !ed25519.Verify(pub, msg, sig) { + return nil, fmt.Errorf("Invalid signature") + } + // TODO: check shard hint + // TODO: check domain hint + return &types.Leaf{ + Message: req.Message, + SigIdent: types.SigIdent{ + Signature: req.Signature, + KeyHash: types.Hash(req.VerificationKey[:]), + }, + }, nil } -func (lp *LogParameters) parseGetProofByHashV1Request(r *http.Request) (*types.GetProofByHashV1, error) { - var item types.GetProofByHashV1 - if err := unpackOctetPost(r, &item); err != nil { +func (lp *LogParameters) parseAddCosignatureRequest(r *http.Request) (*types.CosignatureRequest, error) { + var req types.CosignatureRequest + if err := req.UnmarshalASCII(r.Body); err != nil { return nil, fmt.Errorf("unpackOctetPost: %v", err) } - if item.TreeSize < 1 { - return nil, fmt.Errorf("TreeSize(%d) must be larger than zero", item.TreeSize) + if _, ok := lp.Witnesses[*req.KeyHash]; !ok { + return nil, fmt.Errorf("Unknown witness: %x", req.KeyHash) } - return &item, nil + return &req, nil } -func (lp *LogParameters) parseGetEntriesV1Request(r *http.Request) (*types.GetEntriesV1, error) { - var item types.GetEntriesV1 - if err := unpackOctetPost(r, &item); err != nil { - return nil, fmt.Errorf("unpackOctetPost: %v", err) +func (lp *LogParameters) parseGetConsistencyProofRequest(r *http.Request) (*types.ConsistencyProofRequest, error) { + var req types.ConsistencyProofRequest + if err := req.UnmarshalASCII(r.Body); err != nil { + return nil, fmt.Errorf("UnmarshalASCII: %v", err) } - - if item.Start > item.End { - return nil, fmt.Errorf("start(%v) must be less than or equal to end(%v)", item.Start, item.End) + if req.OldSize < 1 { + return nil, fmt.Errorf("OldSize(%d) must be larger than zero", req.OldSize) } - if item.End-item.Start+1 > uint64(lp.MaxRange) { - item.End = item.Start + uint64(lp.MaxRange) - 1 + if req.NewSize <= req.OldSize { + return nil, fmt.Errorf("NewSize(%d) must be larger than OldSize(%d)", req.NewSize, req.OldSize) } - return &item, nil + return &req, nil } -func unpackOctetPost(r *http.Request, out interface{}) error { - body, err := ioutil.ReadAll(r.Body) - if err != nil { - return fmt.Errorf("failed reading request body: %v", err) - } - if err := types.Unmarshal(body, out); err != nil { - return fmt.Errorf("Unmarshal: %v", err) - } - return nil -} +//func (lp *LogParameters) parseGetProofByHashV1Request(r *http.Request) (*types.GetProofByHashV1, error) { +// var item types.GetProofByHashV1 +// if err := unpackOctetPost(r, &item); err != nil { +// return nil, fmt.Errorf("unpackOctetPost: %v", err) +// } +// if item.TreeSize < 1 { +// return nil, fmt.Errorf("TreeSize(%d) must be larger than zero", item.TreeSize) +// } +// return &item, nil +//} +// +//func (lp *LogParameters) parseGetEntriesV1Request(r *http.Request) (*types.GetEntriesV1, error) { +// var item types.GetEntriesV1 +// if err := unpackOctetPost(r, &item); err != nil { +// return nil, fmt.Errorf("unpackOctetPost: %v", err) +// } +// +// if item.Start > item.End { +// return nil, fmt.Errorf("start(%v) must be less than or equal to end(%v)", item.Start, item.End) +// } +// if item.End-item.Start+1 > uint64(lp.MaxRange) { +// item.End = item.Start + uint64(lp.MaxRange) - 1 +// } +// return &item, nil +//} +// +//func unpackOctetPost(r *http.Request, out interface{}) error { +// body, err := ioutil.ReadAll(r.Body) +// if err != nil { +// return fmt.Errorf("failed reading request body: %v", err) +// } +// if err := types.Unmarshal(body, out); err != nil { +// return fmt.Errorf("Unmarshal: %v", err) +// } +// return nil +//} diff --git a/server/main.go b/server/main.go index 74e4ad3..1fecb43 100644 --- a/server/main.go +++ b/server/main.go @@ -3,19 +3,19 @@ package main import ( "context" + "crypto" + "crypto/ed25519" + "encoding/hex" "flag" "fmt" + "net/http" "os" + "os/signal" "strings" "sync" "syscall" "time" - "crypto/ed25519" - "encoding/base64" - "net/http" - "os/signal" - "github.com/golang/glog" "github.com/google/trillian" "github.com/prometheus/client_golang/prometheus/promhttp" @@ -25,18 +25,15 @@ import ( ) var ( - httpEndpoint = flag.String("http_endpoint", "localhost:6965", "host:port specification of where stfe serves clients") - rpcBackend = flag.String("log_rpc_server", "localhost:6962", "host:port specification of where Trillian serves clients") - prefix = flag.String("prefix", "st/v1", "a prefix that proceeds each endpoint path") - trillianID = flag.Int64("trillian_id", 0, "log identifier in the Trillian database") - deadline = flag.Duration("deadline", time.Second*10, "deadline for backend requests") - key = flag.String("key", "", "base64-encoded Ed25519 signing key") - submitterPolicy = flag.Bool("submitter_policy", false, "whether there is any submitter namespace policy (default: none, accept unregistered submitter namespaces)") - witnessPolicy = flag.Bool("witness_policy", false, "whether there is any witness namespace policy (default: none, accept unregistered witness namespaces)") - submitters = flag.String("submitters", "", "comma-separated list of trusted submitter namespaces in base64 (default: none)") - witnesses = flag.String("witnesses", "", "comma-separated list of trusted submitter namespaces in base64 (default: none)") - maxRange = flag.Int64("max_range", 10, "maximum number of entries that can be retrived in a single request") - interval = flag.Duration("interval", time.Minute*10, "interval used to rotate the log's cosigned STH") + httpEndpoint = flag.String("http_endpoint", "localhost:6965", "host:port specification of where stfe serves clients") + rpcBackend = flag.String("log_rpc_server", "localhost:6962", "host:port specification of where Trillian serves clients") + prefix = flag.String("prefix", "st/v0", "a prefix that proceeds each endpoint path") + trillianID = flag.Int64("trillian_id", 0, "log identifier in the Trillian database") + deadline = flag.Duration("deadline", time.Second*10, "deadline for backend requests") + key = flag.String("key", "", "hex-encoded Ed25519 signing key") + witnesses = flag.String("witnesses", "", "comma-separated list of trusted witness verification keys in hex") + maxRange = flag.Int64("max_range", 10, "maximum number of entries that can be retrived in a single request") + interval = flag.Duration("interval", time.Second*30, "interval used to rotate the log's cosigned STH") ) func main() { @@ -99,30 +96,27 @@ func setupInstanceFromFlags() (*stfe.Instance, error) { // Prometheus metrics glog.V(3).Infof("Adding prometheus handler on path: /metrics") http.Handle("/metrics", promhttp.Handler()) - // Trusted submitters - submitters, err := newNamespacePoolFromString(*submitters) - if err != nil { - return nil, fmt.Errorf("submitters: newNamespacePoolFromString: %v", err) - } // Trusted witnesses - witnesses, err := newNamespacePoolFromString(*witnesses) + witnesses, err := newWitnessMap(*witnesses) if err != nil { - return nil, fmt.Errorf("witnesses: NewNamespacePool: %v", err) + return nil, fmt.Errorf("newWitnessMap: %v", err) } - // Log identity - sk, err := base64.StdEncoding.DecodeString(*key) + // Secret signing key + sk, err := hex.DecodeString(*key) if err != nil { return nil, fmt.Errorf("sk: DecodeString: %v", err) } - signer := ed25519.PrivateKey(sk) - logId, err := types.NewNamespaceEd25519V1([]byte(ed25519.PrivateKey(sk).Public().(ed25519.PublicKey))) - if err != nil { - return nil, fmt.Errorf("NewNamespaceEd25519V1: %v", err) - } // Setup log parameters - lp, err := stfe.NewLogParameters(signer, logId, *trillianID, *prefix, submitters, witnesses, *maxRange, *interval, *deadline, *submitterPolicy, *witnessPolicy) - if err != nil { - return nil, fmt.Errorf("NewLogParameters: %v", err) + lp := &stfe.LogParameters{ + LogId: hex.EncodeToString([]byte(ed25519.PrivateKey(sk).Public().(ed25519.PublicKey))), + TreeId: *trillianID, + Prefix: *prefix, + MaxRange: *maxRange, + Deadline: *deadline, + Interval: *interval, + HashType: crypto.SHA256, + Signer: ed25519.PrivateKey(sk), + Witnesses: witnesses, } // Setup STH source source, err := stfe.NewActiveSthSource(client, lp) @@ -138,28 +132,24 @@ func setupInstanceFromFlags() (*stfe.Instance, error) { return i, nil } -// newNamespacePoolFromString creates a new namespace pool from a -// comma-separated list of serialized and base64-encoded namespaces. -func newNamespacePoolFromString(str string) (*types.NamespacePool, error) { - var namespaces []*types.Namespace - if len(str) > 0 { - for _, b64 := range strings.Split(str, ",") { - b, err := base64.StdEncoding.DecodeString(b64) +// newWitnessMap creates a new map of trusted witnesses +func newWitnessMap(witnesses string) (map[[types.HashSize]byte][types.VerificationKeySize]byte, error) { + w := make(map[[types.HashSize]byte][types.VerificationKeySize]byte) + if len(witnesses) > 0 { + for _, witness := range strings.Split(witnesses, ",") { + b, err := hex.DecodeString(witness) if err != nil { return nil, fmt.Errorf("DecodeString: %v", err) } - var namespace types.Namespace - if err := types.Unmarshal(b, &namespace); err != nil { - return nil, fmt.Errorf("Unmarshal: %v", err) + + var vk [types.VerificationKeySize]byte + if n := copy(vk[:], b); n != types.VerificationKeySize { + return nil, fmt.Errorf("Invalid verification key size: %v", n) } - namespaces = append(namespaces, &namespace) + w[*types.Hash(vk[:])] = vk } } - pool, err := types.NewNamespacePool(namespaces) - if err != nil { - return nil, fmt.Errorf("NewNamespacePool: %v", err) - } - return pool, nil + return w, nil } // await waits for a shutdown signal and then runs a clean-up function diff --git a/sth.go b/sth.go index ee4dd2f..1399241 100644 --- a/sth.go +++ b/sth.go @@ -2,6 +2,7 @@ package stfe import ( "context" + "crypto/ed25519" "fmt" "reflect" "sync" @@ -13,34 +14,32 @@ import ( "github.com/system-transparency/stfe/types" ) -// SthSource provides access to the log's STHs. +// SthSource provides access to the log's (co)signed tree heads type SthSource interface { - // Latest returns the most reccent signed_tree_head_v*. - Latest(context.Context) (*types.StItem, error) - // Stable returns the most recent signed_tree_head_v* that is stable for - // some period of time, e.g., 10 minutes. - Stable(context.Context) (*types.StItem, error) - // Cosigned returns the most recent cosigned_tree_head_v*. - Cosigned(context.Context) (*types.StItem, error) - // AddCosignature attempts to add a cosignature to the stable STH. The - // passed cosigned_tree_head_v* must have a single verified cosignature. - AddCosignature(context.Context, *types.StItem) error - // Run keeps the STH source updated until cancelled + Latest(context.Context) (*types.SignedTreeHead, error) + Stable(context.Context) (*types.SignedTreeHead, error) + Cosigned(context.Context) (*types.SignedTreeHead, error) + AddCosignature(context.Context, ed25519.PublicKey, *[types.SignatureSize]byte) error Run(context.Context) } // ActiveSthSource implements the SthSource interface for an STFE instance that // accepts new logging requests, i.e., the log is running in read+write mode. type ActiveSthSource struct { - client trillian.TrillianLogClient - logParameters *LogParameters - currCosth *types.StItem // current cosigned_tree_head_v1 (already finalized) - nextCosth *types.StItem // next cosigned_tree_head_v1 (under preparation) - cosignatureFrom map[[types.NamespaceFingerprintSize]byte]bool // track who we got cosignatures from in nextCosth - mutex sync.RWMutex + client trillian.TrillianLogClient + logParameters *LogParameters + sync.RWMutex + + // cosigned is the current cosigned tree head that is served + cosigned types.SignedTreeHead + + // tosign is the current tree head that is being cosigned + tosign types.SignedTreeHead + + // cosignature keeps track of all collected cosignatures for tosign + cosignature map[[types.HashSize]byte]*types.SigIdent } -// NewActiveSthSource returns an initialized ActiveSthSource func NewActiveSthSource(cli trillian.TrillianLogClient, lp *LogParameters) (*ActiveSthSource, error) { s := ActiveSthSource{ client: cli, @@ -53,10 +52,9 @@ func NewActiveSthSource(cli trillian.TrillianLogClient, lp *LogParameters) (*Act return nil, fmt.Errorf("Latest: %v", err) } - // TODO: load persisted cosigned STH? - s.currCosth = types.NewCosignedTreeHeadV1(sth.SignedTreeHeadV1, nil) - s.nextCosth = types.NewCosignedTreeHeadV1(sth.SignedTreeHeadV1, nil) - s.cosignatureFrom = make(map[[types.NamespaceFingerprintSize]byte]bool) + s.cosigned = *sth + s.tosign = *sth + s.cosignature = make(map[[types.HashSize]byte]*types.SigIdent) return &s, nil } @@ -70,16 +68,13 @@ func (s *ActiveSthSource) Run(ctx context.Context) { return } // rotate - s.mutex.Lock() - defer s.mutex.Unlock() - if err := s.rotate(sth); err != nil { - glog.Warningf("rotate failed: %v", err) - } - // TODO: persist cosigned STH? + s.Lock() + defer s.Unlock() + s.rotate(sth) }) } -func (s *ActiveSthSource) Latest(ctx context.Context) (*types.StItem, error) { +func (s *ActiveSthSource) Latest(ctx context.Context) (*types.SignedTreeHead, error) { trsp, err := s.client.GetLatestSignedLogRoot(ctx, &trillian.GetLatestSignedLogRootRequest{ LogId: s.logParameters.TreeId, }) @@ -87,69 +82,62 @@ func (s *ActiveSthSource) Latest(ctx context.Context) (*types.StItem, error) { if errInner := checkGetLatestSignedLogRoot(s.logParameters, trsp, err, &lr); errInner != nil { return nil, fmt.Errorf("invalid signed log root response: %v", errInner) } - return s.logParameters.SignTreeHeadV1(NewTreeHeadV1FromLogRoot(&lr)) + return s.logParameters.Sign(NewTreeHeadFromLogRoot(&lr)) } -func (s *ActiveSthSource) Stable(_ context.Context) (*types.StItem, error) { - s.mutex.RLock() - defer s.mutex.RUnlock() - if s.nextCosth == nil { - return nil, fmt.Errorf("no stable sth available") - } - return types.NewSignedTreeHeadV1(&s.nextCosth.CosignedTreeHeadV1.SignedTreeHead.TreeHead, &s.nextCosth.CosignedTreeHeadV1.SignedTreeHead.Signature), nil +func (s *ActiveSthSource) Stable(_ context.Context) (*types.SignedTreeHead, error) { + s.RLock() + defer s.RUnlock() + return &s.tosign, nil } -func (s *ActiveSthSource) Cosigned(_ context.Context) (*types.StItem, error) { - s.mutex.RLock() - defer s.mutex.RUnlock() - if s.currCosth == nil { - return nil, fmt.Errorf("no cosigned sth available") - } - return s.currCosth, nil +func (s *ActiveSthSource) Cosigned(_ context.Context) (*types.SignedTreeHead, error) { + s.RLock() + defer s.RUnlock() + return &s.cosigned, nil } -func (s *ActiveSthSource) AddCosignature(_ context.Context, costh *types.StItem) error { - s.mutex.Lock() - defer s.mutex.Unlock() - if !reflect.DeepEqual(s.nextCosth.CosignedTreeHeadV1.SignedTreeHead, costh.CosignedTreeHeadV1.SignedTreeHead) { - return fmt.Errorf("cosignature covers a different tree head") - } - witness, err := costh.CosignedTreeHeadV1.Cosignatures[0].Namespace.Fingerprint() - if err != nil { - return fmt.Errorf("namespace without fingerprint: %v", err) +func (s *ActiveSthSource) AddCosignature(_ context.Context, vk ed25519.PublicKey, sig *[types.SignatureSize]byte) error { + s.Lock() + defer s.Unlock() + + if msg := s.tosign.TreeHead.Marshal(); !ed25519.Verify(vk, msg, sig[:]) { + return fmt.Errorf("Invalid signature for tree head with timestamp: %d", s.tosign.TreeHead.Timestamp) } - if _, ok := s.cosignatureFrom[*witness]; ok { + witness := types.Hash(vk[:]) + if _, ok := s.cosignature[*witness]; ok { + glog.V(3).Infof("received cosignature again (duplicate)") return nil // duplicate } - s.cosignatureFrom[*witness] = true - s.nextCosth.CosignedTreeHeadV1.Cosignatures = append(s.nextCosth.CosignedTreeHeadV1.Cosignatures, costh.CosignedTreeHeadV1.Cosignatures[0]) + s.cosignature[*witness] = &types.SigIdent{ + Signature: sig, + KeyHash: witness, + } + glog.V(3).Infof("accepted new cosignature") return nil } // rotate rotates the log's cosigned and stable STH. The caller must aquire the // source's read-write lock if there are concurrent reads and/or writes. -func (s *ActiveSthSource) rotate(fixedSth *types.StItem) error { - // rotate stable -> cosigned - if reflect.DeepEqual(&s.currCosth.CosignedTreeHeadV1.SignedTreeHead, &s.nextCosth.CosignedTreeHeadV1.SignedTreeHead) { - for _, sigv1 := range s.currCosth.CosignedTreeHeadV1.Cosignatures { - witness, err := sigv1.Namespace.Fingerprint() - if err != nil { - return fmt.Errorf("namespace without fingerprint: %v", err) - } - if _, ok := s.cosignatureFrom[*witness]; !ok { - s.cosignatureFrom[*witness] = true - s.nextCosth.CosignedTreeHeadV1.Cosignatures = append(s.nextCosth.CosignedTreeHeadV1.Cosignatures, sigv1) +func (s *ActiveSthSource) rotate(next *types.SignedTreeHead) { + if reflect.DeepEqual(s.cosigned.TreeHead, s.tosign.TreeHead) { + for _, sigident := range s.cosigned.SigIdent[1:] { // skip log sigident + if _, ok := s.cosignature[*sigident.KeyHash]; !ok { + s.cosignature[*sigident.KeyHash] = sigident } } } - s.currCosth.CosignedTreeHeadV1.SignedTreeHead = s.nextCosth.CosignedTreeHeadV1.SignedTreeHead - s.currCosth.CosignedTreeHeadV1.Cosignatures = make([]types.SignatureV1, len(s.nextCosth.CosignedTreeHeadV1.Cosignatures)) - copy(s.currCosth.CosignedTreeHeadV1.Cosignatures, s.nextCosth.CosignedTreeHeadV1.Cosignatures) - - // rotate new stable -> stable - if !reflect.DeepEqual(&s.nextCosth.CosignedTreeHeadV1.SignedTreeHead, fixedSth.SignedTreeHeadV1) { - s.nextCosth = types.NewCosignedTreeHeadV1(fixedSth.SignedTreeHeadV1, nil) - s.cosignatureFrom = make(map[[types.NamespaceFingerprintSize]byte]bool) - } - return nil + var cosignatures []*types.SigIdent + for _, sigident := range s.cosignature { + cosignatures = append(cosignatures, sigident) + } // cosignatures contains all cosignatures, even if repeated tree head + + // Update cosigned tree head + s.cosigned.TreeHead = s.tosign.TreeHead + s.cosigned.SigIdent = append(s.tosign.SigIdent, cosignatures...) + + // Update to-sign tree head + s.tosign = *next + s.cosignature = make(map[[types.HashSize]byte]*types.SigIdent) + glog.V(3).Infof("rotated sth") } diff --git a/trillian.go b/trillian.go index 2adf567..f358d4d 100644 --- a/trillian.go +++ b/trillian.go @@ -26,7 +26,7 @@ func checkQueueLeaf(rsp *trillian.QueueLeafResponse, err error) error { return nil } -func checkGetLeavesByRange(req *stfetypes.GetEntriesV1, rsp *trillian.GetLeavesByRangeResponse, err error) error { +func checkGetLeavesByRange(req *stfetypes.LeavesRequest, rsp *trillian.GetLeavesByRangeResponse, err error) error { if err != nil { return fmt.Errorf("Trillian Error: %v", err) } @@ -42,8 +42,8 @@ func checkGetLeavesByRange(req *stfetypes.GetEntriesV1, rsp *trillian.GetLeavesB if len(rsp.Leaves) == 0 { return fmt.Errorf("Trillian error: no leaves") } - if len(rsp.Leaves) > int(req.End-req.Start+1) { - return fmt.Errorf("too many leaves: %d for [%d,%d]", len(rsp.Leaves), req.Start, req.End) + if len(rsp.Leaves) > int(req.EndSize-req.StartSize+1) { + return fmt.Errorf("too many leaves: %d for [%d,%d]", len(rsp.Leaves), req.StartSize, req.EndSize) } // Ensure that a bad start parameter results in an error @@ -51,13 +51,13 @@ func checkGetLeavesByRange(req *stfetypes.GetEntriesV1, rsp *trillian.GetLeavesB if err := lr.UnmarshalBinary(rsp.SignedLogRoot.LogRoot); err != nil { return fmt.Errorf("cannot unmarshal log root: %v", err) } - if uint64(req.Start) >= lr.TreeSize { - return fmt.Errorf("invalid start(%d): tree size is %d", req.Start, lr.TreeSize) + if uint64(req.StartSize) >= lr.TreeSize { + return fmt.Errorf("invalid start(%d): tree size is %d", req.StartSize, lr.TreeSize) } // Ensure that we got and return expected leaf indices for i, leaf := range rsp.Leaves { - if got, want := leaf.LeafIndex, int64(req.Start+uint64(i)); got != want { + if got, want := leaf.LeafIndex, int64(req.StartSize+uint64(i)); got != want { return fmt.Errorf("invalid leaf index(%d): wanted %d", got, want) } } diff --git a/util.go b/util.go index 847c3f7..b5cca48 100644 --- a/util.go +++ b/util.go @@ -1,40 +1,42 @@ package stfe import ( - "fmt" + //"fmt" - "github.com/google/trillian" + //"github.com/google/trillian" ttypes "github.com/google/trillian/types" "github.com/system-transparency/stfe/types" ) -func NewTreeHeadV1FromLogRoot(lr *ttypes.LogRootV1) *types.TreeHeadV1 { - return &types.TreeHeadV1{ - Timestamp: uint64(lr.TimestampNanos / 1000 / 1000), +func NewTreeHeadFromLogRoot(lr *ttypes.LogRootV1) *types.TreeHead { + var hash [types.HashSize]byte + th := types.TreeHead{ + Timestamp: uint64(lr.TimestampNanos / 1000 / 1000 / 1000), TreeSize: uint64(lr.TreeSize), - RootHash: types.NodeHash{ - Data: lr.RootHash, - }, - Extension: make([]byte, 0), + RootHash: &hash, } + copy(th.RootHash[:], lr.RootHash) + return &th } -func NewNodePathFromHashPath(hashes [][]byte) []types.NodeHash { - path := make([]types.NodeHash, 0, len(hashes)) +func NodePathFromHashes(hashes [][]byte) []*[types.HashSize]byte { + var path []*[types.HashSize]byte for _, hash := range hashes { - path = append(path, types.NodeHash{hash}) + var h [types.HashSize]byte + copy(h[:], hash) + path = append(path, &h) } return path } -func NewStItemListFromLeaves(leaves []*trillian.LogLeaf) (*types.StItemList, error) { - items := make([]types.StItem, 0, len(leaves)) - for _, leaf := range leaves { - var item types.StItem - if err := types.Unmarshal(leaf.LeafValue, &item); err != nil { - return nil, fmt.Errorf("Unmarshal failed: %v", err) - } - items = append(items, item) - } - return &types.StItemList{items}, nil -} +//func NewStItemListFromLeaves(leaves []*trillian.LogLeaf) (*types.StItemList, error) { +// items := make([]types.StItem, 0, len(leaves)) +// for _, leaf := range leaves { +// var item types.StItem +// if err := types.Unmarshal(leaf.LeafValue, &item); err != nil { +// return nil, fmt.Errorf("Unmarshal failed: %v", err) +// } +// items = append(items, item) +// } +// return &types.StItemList{items}, nil +//} -- cgit v1.2.3 From 6aa7e4879ad72bfe5e344f6a55b22837fd6ce2bd Mon Sep 17 00:00:00 2001 From: Rasmus Dahlberg Date: Tue, 1 Jun 2021 00:24:07 +0200 Subject: added basic commands to interact with stfe server --- client/cmd/cosign/main.go | 56 +++++++++++++++++++++++++++++++++++++++++++++++ client/cmd/submit/main.go | 28 ++++++++++++++++++++++++ 2 files changed, 84 insertions(+) create mode 100644 client/cmd/cosign/main.go create mode 100644 client/cmd/submit/main.go diff --git a/client/cmd/cosign/main.go b/client/cmd/cosign/main.go new file mode 100644 index 0000000..e86842b --- /dev/null +++ b/client/cmd/cosign/main.go @@ -0,0 +1,56 @@ +package main + +import ( + "bytes" + "crypto/ed25519" + "encoding/hex" + "flag" + "fmt" + "log" + "net/http" + + "github.com/system-transparency/stfe/types" +) + +var ( + url = flag.String("url", "http://localhost:6965/st/v0", "base url") + sk = flag.String("sk", "e1d7c494dacb0ddf809a17e4528b01f584af22e3766fa740ec52a1711c59500d711090dd2286040b50961b0fe09f58aa665ccee5cb7ee042d819f18f6ab5046b", "hex key") +) + +func main() { + priv, err := hex.DecodeString(*sk) + if err != nil { + log.Fatalf("DecodeString: %v", err) + } + sk := ed25519.PrivateKey(priv) + vk := sk.Public().(ed25519.PublicKey) + fmt.Printf("sk: %x\nvk: %x\n", sk, vk) + + rsp, err := http.Get(*url + "/get-tree-head-to-sign") + if err != nil { + log.Fatalf("Get: %v", err) + } + var sth types.SignedTreeHead + if err := sth.UnmarshalASCII(rsp.Body); err != nil { + log.Fatalf("UnmarshalASCII: %v", err) + } + fmt.Printf("%+v\n", sth) + + msg := sth.TreeHead.Marshal() + sig := ed25519.Sign(sk, msg) + sigident := &types.SigIdent{ + KeyHash: types.Hash(vk[:]), + Signature: &[types.SignatureSize]byte{}, + } + copy(sigident.Signature[:], sig) + + buf := bytes.NewBuffer(nil) + if err := sigident.MarshalASCII(buf); err != nil { + log.Fatalf("MarshalASCII: %v", err) + } + rsp, err = http.Post(*url+"/add-cosignature", "type/stfe", buf) + if err != nil { + log.Fatalf("Post: %v", err) + } + fmt.Printf("Status: %v\n", rsp.StatusCode) +} diff --git a/client/cmd/submit/main.go b/client/cmd/submit/main.go new file mode 100644 index 0000000..36c7271 --- /dev/null +++ b/client/cmd/submit/main.go @@ -0,0 +1,28 @@ +package main + +// go run . | bash + +import ( + "crypto/ed25519" + "crypto/rand" + "fmt" + "github.com/system-transparency/stfe/types" +) + +func main() { + checksum := [32]byte{} + msg := types.Message{ + ShardHint: 0, + Checksum: &checksum, + } + + vk, sk, err := ed25519.GenerateKey(rand.Reader) + if err != nil { + fmt.Printf("ed25519.GenerateKey: %v\n", err) + return + } + sig := ed25519.Sign(sk, msg.Marshal()) + //fmt.Printf("sk: %x\nvk: %x\n", sk[:], vk[:]) + + fmt.Printf("echo \"shard_hint=%d\nchecksum=%x\nsignature_over_message=%x\nverification_key=%x\ndomain_hint=%s\" | curl --data-binary @- localhost:6965/st/v0/add-leaf\n", msg.ShardHint, msg.Checksum[:], sig, vk[:], "example.com") +} -- cgit v1.2.3 From b853bb6095179c56c8605b1da920b5b3bda21f67 Mon Sep 17 00:00:00 2001 From: Rasmus Dahlberg Date: Tue, 1 Jun 2021 10:20:24 +0200 Subject: use ioutil instead of io (go v1.14) --- types/ascii.go | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/types/ascii.go b/types/ascii.go index 565e52d..d27d79b 100644 --- a/types/ascii.go +++ b/types/ascii.go @@ -5,6 +5,7 @@ import ( "encoding/hex" "fmt" "io" + "io/ioutil" "strconv" ) @@ -64,7 +65,7 @@ type MessageASCII struct { // NewMessageASCII unpacks an incoming ASCII message func NewMessageASCII(r io.Reader, numFieldExpected int) (*MessageASCII, error) { - buf, err := io.ReadAll(r) + buf, err := ioutil.ReadAll(r) if err != nil { return nil, fmt.Errorf("ReadAll: %v", err) } -- cgit v1.2.3 From 17a1cf31764541daa509cf59f1bbb32c25b28c8c Mon Sep 17 00:00:00 2001 From: Rasmus Dahlberg Date: Tue, 1 Jun 2021 10:41:29 +0200 Subject: added keygen command --- client/cmd/keygen/main.go | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) create mode 100644 client/cmd/keygen/main.go diff --git a/client/cmd/keygen/main.go b/client/cmd/keygen/main.go new file mode 100644 index 0000000..c1c1b58 --- /dev/null +++ b/client/cmd/keygen/main.go @@ -0,0 +1,17 @@ +package main + +import ( + "crypto/ed25519" + "crypto/rand" + "fmt" + "log" +) + +func main() { + vk, sk, err := ed25519.GenerateKey(rand.Reader) + if err != nil { + log.Fatalf("GenerateKey: %v", err) + } + fmt.Printf("sk: %x\n", sk[:]) + fmt.Printf("vk: %x\n", vk[:]) +} -- cgit v1.2.3 From 2c2c6936a93ce4f18302426a665ed5782910ab85 Mon Sep 17 00:00:00 2001 From: Rasmus Dahlberg Date: Tue, 1 Jun 2021 21:11:34 +0200 Subject: re-added all endpoints To be tested in greater detail. --- endpoint.go | 113 +++++++++++++++++++++++++++++------------------------------- instance.go | 4 +-- request.go | 61 ++++++++++++++------------------ util.go | 15 -------- 4 files changed, 82 insertions(+), 111 deletions(-) diff --git a/endpoint.go b/endpoint.go index 98a5ce8..19ea388 100644 --- a/endpoint.go +++ b/endpoint.go @@ -127,61 +127,58 @@ func getConsistencyProof(ctx context.Context, i *Instance, w http.ResponseWriter return http.StatusOK, nil } -//func getProofByHash(ctx context.Context, i *Instance, w http.ResponseWriter, r *http.Request) (int, error) { -// glog.V(3).Info("handling get-proof-by-hash request") -// req, err := i.LogParameters.parseGetProofByHashV1Request(r) -// if err != nil { -// return http.StatusBadRequest, err -// } -// -// trsp, err := i.Client.GetInclusionProofByHash(ctx, &trillian.GetInclusionProofByHashRequest{ -// LogId: i.LogParameters.TreeId, -// LeafHash: req.Hash[:], -// TreeSize: int64(req.TreeSize), -// OrderBySequence: true, -// }) -// if errInner := checkGetInclusionProofByHash(i.LogParameters, trsp, err); errInner != nil { -// return http.StatusInternalServerError, fmt.Errorf("bad GetInclusionProofByHashResponse: %v", errInner) -// } -// -// if err := writeOctetResponse(w, *types.NewInclusionProofV1(i.LogParameters.LogId, req.TreeSize, uint64(trsp.Proof[0].LeafIndex), NewNodePathFromHashPath(trsp.Proof[0].Hashes))); err != nil { -// return http.StatusInternalServerError, fmt.Errorf("writeOctetResponse: %v", err) -// } -// return http.StatusOK, nil -//} -// -//func getEntries(ctx context.Context, i *Instance, w http.ResponseWriter, r *http.Request) (int, error) { -// glog.V(3).Info("handling get-entries request") -// req, err := i.LogParameters.parseGetEntriesV1Request(r) -// if err != nil { -// return http.StatusBadRequest, err -// } -// -// trsp, err := i.Client.GetLeavesByRange(ctx, &trillian.GetLeavesByRangeRequest{ -// LogId: i.LogParameters.TreeId, -// StartIndex: int64(req.Start), -// Count: int64(req.End-req.Start) + 1, -// }) -// if errInner := checkGetLeavesByRange(req, trsp, err); errInner != nil { -// return http.StatusInternalServerError, fmt.Errorf("checkGetLeavesByRangeResponse: %v", errInner) // there is one StatusBadRequest in here tho.. -// } -// -// if rsp, err := NewStItemListFromLeaves(trsp.Leaves); err != nil { -// return http.StatusInternalServerError, fmt.Errorf("NewStItemListFromLeaves: %v", err) // should never happen -// } else if err := writeOctetResponse(w, *rsp); err != nil { -// return http.StatusInternalServerError, fmt.Errorf("writeOctetResponse: %v", err) -// } -// return http.StatusOK, nil -//} -// -//func writeOctetResponse(w http.ResponseWriter, i interface{}) error { -// b, err := types.Marshal(i) -// if err != nil { -// return fmt.Errorf("Marshal: %v", err) -// } -// w.Header().Set("Content-Type", "application/octet-stream") -// if _, err := w.Write(b); err != nil { -// return fmt.Errorf("Write: %v", err) -// } -// return nil -//} +func getProofByHash(ctx context.Context, i *Instance, w http.ResponseWriter, r *http.Request) (int, error) { + glog.V(3).Info("handling get-proof-by-hash request") + req, err := i.LogParameters.parseGetProofByHashRequest(r) + if err != nil { + return http.StatusBadRequest, err + } + + trsp, err := i.Client.GetInclusionProofByHash(ctx, &trillian.GetInclusionProofByHashRequest{ + LogId: i.LogParameters.TreeId, + LeafHash: req.LeafHash[:], + TreeSize: int64(req.TreeSize), + OrderBySequence: true, + }) + if errInner := checkGetInclusionProofByHash(i.LogParameters, trsp, err); errInner != nil { + return http.StatusInternalServerError, fmt.Errorf("bad GetInclusionProofByHashResponse: %v", errInner) + } + + proof := &types.InclusionProof{ + TreeSize: req.TreeSize, + LeafIndex: uint64(trsp.Proof[0].LeafIndex), + Path: NodePathFromHashes(trsp.Proof[0].Hashes), + } + if err := proof.MarshalASCII(w); err != nil { + return http.StatusInternalServerError, fmt.Errorf("MarshalASCII: %v", err) + } + return http.StatusOK, nil +} + +func getEntries(ctx context.Context, i *Instance, w http.ResponseWriter, r *http.Request) (int, error) { + glog.V(3).Info("handling get-entries request") + req, err := i.LogParameters.parseGetEntriesRequest(r) + if err != nil { + return http.StatusBadRequest, err + } + + trsp, err := i.Client.GetLeavesByRange(ctx, &trillian.GetLeavesByRangeRequest{ + LogId: i.LogParameters.TreeId, + StartIndex: int64(req.StartSize), + Count: int64(req.EndSize-req.StartSize) + 1, + }) + if errInner := checkGetLeavesByRange(req, trsp, err); errInner != nil { + return http.StatusInternalServerError, fmt.Errorf("checkGetLeavesByRangeResponse: %v", errInner) // there is one StatusBadRequest in here tho.. + } + + for _, serialized := range trsp.Leaves { + var leaf types.Leaf + if err := leaf.Unmarshal(serialized.LeafValue); err != nil { + return http.StatusInternalServerError, fmt.Errorf("Unmarshal: %v", err) + } + if err := leaf.MarshalASCII(w); err != nil { + return http.StatusInternalServerError, fmt.Errorf("MarshalASCII: %v", err) + } + } + return http.StatusOK, nil +} diff --git a/instance.go b/instance.go index d11032e..5d358c1 100644 --- a/instance.go +++ b/instance.go @@ -27,9 +27,9 @@ func (i *Instance) Handlers() []Handler { Handler{Instance: i, Handler: getLatestSth, Endpoint: EndpointGetLatestSth, Method: http.MethodGet}, Handler{Instance: i, Handler: getStableSth, Endpoint: EndpointGetStableSth, Method: http.MethodGet}, Handler{Instance: i, Handler: getCosignedSth, Endpoint: EndpointGetCosignedSth, Method: http.MethodGet}, - //Handler{Instance: i, Handler: getProofByHash, Endpoint: EndpointGetProofByHash, Method: http.MethodPost}, + Handler{Instance: i, Handler: getProofByHash, Endpoint: EndpointGetProofByHash, Method: http.MethodPost}, Handler{Instance: i, Handler: getConsistencyProof, Endpoint: EndpointGetConsistencyProof, Method: http.MethodPost}, - //Handler{Instance: i, Handler: getEntries, Endpoint: EndpointGetEntries, Method: http.MethodPost}, + Handler{Instance: i, Handler: getEntries, Endpoint: EndpointGetEntries, Method: http.MethodPost}, } } diff --git a/request.go b/request.go index 3b57779..763d9ed 100644 --- a/request.go +++ b/request.go @@ -54,39 +54,28 @@ func (lp *LogParameters) parseGetConsistencyProofRequest(r *http.Request) (*type return &req, nil } -//func (lp *LogParameters) parseGetProofByHashV1Request(r *http.Request) (*types.GetProofByHashV1, error) { -// var item types.GetProofByHashV1 -// if err := unpackOctetPost(r, &item); err != nil { -// return nil, fmt.Errorf("unpackOctetPost: %v", err) -// } -// if item.TreeSize < 1 { -// return nil, fmt.Errorf("TreeSize(%d) must be larger than zero", item.TreeSize) -// } -// return &item, nil -//} -// -//func (lp *LogParameters) parseGetEntriesV1Request(r *http.Request) (*types.GetEntriesV1, error) { -// var item types.GetEntriesV1 -// if err := unpackOctetPost(r, &item); err != nil { -// return nil, fmt.Errorf("unpackOctetPost: %v", err) -// } -// -// if item.Start > item.End { -// return nil, fmt.Errorf("start(%v) must be less than or equal to end(%v)", item.Start, item.End) -// } -// if item.End-item.Start+1 > uint64(lp.MaxRange) { -// item.End = item.Start + uint64(lp.MaxRange) - 1 -// } -// return &item, nil -//} -// -//func unpackOctetPost(r *http.Request, out interface{}) error { -// body, err := ioutil.ReadAll(r.Body) -// if err != nil { -// return fmt.Errorf("failed reading request body: %v", err) -// } -// if err := types.Unmarshal(body, out); err != nil { -// return fmt.Errorf("Unmarshal: %v", err) -// } -// return nil -//} +func (lp *LogParameters) parseGetProofByHashRequest(r *http.Request) (*types.InclusionProofRequest, error) { + var req types.InclusionProofRequest + if err := req.UnmarshalASCII(r.Body); err != nil { + return nil, fmt.Errorf("UnmarshalASCII: %v", err) + } + if req.TreeSize < 1 { + return nil, fmt.Errorf("TreeSize(%d) must be larger than zero", req.TreeSize) + } + return &req, nil +} + +func (lp *LogParameters) parseGetEntriesRequest(r *http.Request) (*types.LeavesRequest, error) { + var req types.LeavesRequest + if err := req.UnmarshalASCII(r.Body); err != nil { + return nil, fmt.Errorf("UnmarshalASCII: %v", err) + } + + if req.StartSize > req.EndSize { + return nil, fmt.Errorf("StartSize(%d) must be less than or equal to EndSize(%d)", req.StartSize, req.EndSize) + } + if req.EndSize-req.StartSize+1 > uint64(lp.MaxRange) { + req.EndSize = req.StartSize + uint64(lp.MaxRange) - 1 + } + return &req, nil +} diff --git a/util.go b/util.go index b5cca48..a8c918e 100644 --- a/util.go +++ b/util.go @@ -1,9 +1,6 @@ package stfe import ( - //"fmt" - - //"github.com/google/trillian" ttypes "github.com/google/trillian/types" "github.com/system-transparency/stfe/types" ) @@ -28,15 +25,3 @@ func NodePathFromHashes(hashes [][]byte) []*[types.HashSize]byte { } return path } - -//func NewStItemListFromLeaves(leaves []*trillian.LogLeaf) (*types.StItemList, error) { -// items := make([]types.StItem, 0, len(leaves)) -// for _, leaf := range leaves { -// var item types.StItem -// if err := types.Unmarshal(leaf.LeafValue, &item); err != nil { -// return nil, fmt.Errorf("Unmarshal failed: %v", err) -// } -// items = append(items, item) -// } -// return &types.StItemList{items}, nil -//} -- cgit v1.2.3 From 2dcaf038655426c572c5d292973001c581f0a1a2 Mon Sep 17 00:00:00 2001 From: Rasmus Dahlberg Date: Tue, 1 Jun 2021 21:37:54 +0200 Subject: moved endpoint type into types --- endpoint.go | 21 --------------------- endpoint_test.go | 48 ------------------------------------------------ instance.go | 18 +++++++++--------- types/types.go | 41 +++++++++++++++++++---------------------- types/types_test.go | 53 +++++++++++++++++++++++++++++++++++++++++++++++++++++ 5 files changed, 81 insertions(+), 100 deletions(-) create mode 100644 types/types_test.go diff --git a/endpoint.go b/endpoint.go index 19ea388..9be55b4 100644 --- a/endpoint.go +++ b/endpoint.go @@ -5,33 +5,12 @@ import ( "crypto/ed25519" "fmt" "net/http" - "strings" "github.com/golang/glog" "github.com/google/trillian" "github.com/system-transparency/stfe/types" ) -// Endpoint is a named HTTP API endpoint -type Endpoint string - -const ( - EndpointAddEntry = Endpoint("add-leaf") - EndpointAddCosignature = Endpoint("add-cosignature") - EndpointGetLatestSth = Endpoint("get-tree-head-latest") - EndpointGetStableSth = Endpoint("get-tree-head-to-sign") - EndpointGetCosignedSth = Endpoint("get-tree-head-cosigned") - EndpointGetProofByHash = Endpoint("get-proof-by-hash") - EndpointGetConsistencyProof = Endpoint("get-consistency-proof") - EndpointGetEntries = Endpoint("get-leaves") -) - -// Path joins a number of components to form a full endpoint path, e.g., base -// ("example.com"), prefix ("st/v1"), and the endpoint itself ("get-sth"). -func (e Endpoint) Path(components ...string) string { - return strings.Join(append(components, string(e)), "/") -} - func addEntry(ctx context.Context, i *Instance, w http.ResponseWriter, r *http.Request) (int, error) { glog.V(3).Info("handling add-entry request") leaf, err := i.LogParameters.parseAddEntryV1Request(r) diff --git a/endpoint_test.go b/endpoint_test.go index aab2c54..e515635 100644 --- a/endpoint_test.go +++ b/endpoint_test.go @@ -452,54 +452,6 @@ func TestEndpointGetEntriesV1(t *testing.T) { } } -func TestEndpointPath(t *testing.T) { - base, prefix, proto := "http://example.com", "test", "st/v1" - for _, table := range []struct { - endpoint Endpoint - want string - }{ - { - endpoint: EndpointAddEntry, - want: "http://example.com/test/st/v1/add-entry", - }, - { - endpoint: EndpointAddCosignature, - want: "http://example.com/test/st/v1/add-cosignature", - }, - { - endpoint: EndpointGetLatestSth, - want: "http://example.com/test/st/v1/get-latest-sth", - }, - { - endpoint: EndpointGetStableSth, - want: "http://example.com/test/st/v1/get-stable-sth", - }, - { - endpoint: EndpointGetCosignedSth, - want: "http://example.com/test/st/v1/get-cosigned-sth", - }, - { - endpoint: EndpointGetConsistencyProof, - want: "http://example.com/test/st/v1/get-consistency-proof", - }, - { - endpoint: EndpointGetProofByHash, - want: "http://example.com/test/st/v1/get-proof-by-hash", - }, - { - endpoint: EndpointGetEntries, - want: "http://example.com/test/st/v1/get-entries", - }, - } { - if got, want := table.endpoint.Path(base+"/"+prefix+"/"+proto), table.want; got != want { - t.Errorf("got endpoint\n%s\n\tbut wanted\n%s\n\twith one component", got, want) - } - if got, want := table.endpoint.Path(base, prefix, proto), table.want; got != want { - t.Errorf("got endpoint\n%s\n\tbut wanted\n%s\n\tmultiple components", got, want) - } - } -} - // TODO: TestWriteOctetResponse func TestWriteOctetResponse(t *testing.T) { } diff --git a/instance.go b/instance.go index 5d358c1..4425770 100644 --- a/instance.go +++ b/instance.go @@ -22,14 +22,14 @@ type Instance struct { // Handlers returns a list of STFE handlers func (i *Instance) Handlers() []Handler { return []Handler{ - Handler{Instance: i, Handler: addEntry, Endpoint: EndpointAddEntry, Method: http.MethodPost}, - Handler{Instance: i, Handler: addCosignature, Endpoint: EndpointAddCosignature, Method: http.MethodPost}, - Handler{Instance: i, Handler: getLatestSth, Endpoint: EndpointGetLatestSth, Method: http.MethodGet}, - Handler{Instance: i, Handler: getStableSth, Endpoint: EndpointGetStableSth, Method: http.MethodGet}, - Handler{Instance: i, Handler: getCosignedSth, Endpoint: EndpointGetCosignedSth, Method: http.MethodGet}, - Handler{Instance: i, Handler: getProofByHash, Endpoint: EndpointGetProofByHash, Method: http.MethodPost}, - Handler{Instance: i, Handler: getConsistencyProof, Endpoint: EndpointGetConsistencyProof, Method: http.MethodPost}, - Handler{Instance: i, Handler: getEntries, Endpoint: EndpointGetEntries, Method: http.MethodPost}, + Handler{Instance: i, Handler: addEntry, Endpoint: types.EndpointAddLeaf, Method: http.MethodPost}, + Handler{Instance: i, Handler: addCosignature, Endpoint: types.EndpointAddCosignature, Method: http.MethodPost}, + Handler{Instance: i, Handler: getLatestSth, Endpoint: types.EndpointGetTreeHeadLatest, Method: http.MethodGet}, + Handler{Instance: i, Handler: getStableSth, Endpoint: types.EndpointGetTreeHeadToSign, Method: http.MethodGet}, + Handler{Instance: i, Handler: getCosignedSth, Endpoint: types.EndpointGetTreeHeadCosigned, Method: http.MethodGet}, + Handler{Instance: i, Handler: getProofByHash, Endpoint: types.EndpointGetProofByHash, Method: http.MethodPost}, + Handler{Instance: i, Handler: getConsistencyProof, Endpoint: types.EndpointGetConsistencyProof, Method: http.MethodPost}, + Handler{Instance: i, Handler: getEntries, Endpoint: types.EndpointGetLeaves, Method: http.MethodPost}, } } @@ -37,7 +37,7 @@ func (i *Instance) Handlers() []Handler { // to an STFE server instance as well as a function that uses it. type Handler struct { Instance *Instance - Endpoint Endpoint + Endpoint types.Endpoint Method string Handler func(context.Context, *Instance, http.ResponseWriter, *http.Request) (int, error) } diff --git a/types/types.go b/types/types.go index 2da40da..d031b29 100644 --- a/types/types.go +++ b/types/types.go @@ -3,17 +3,34 @@ package types import ( "crypto/ed25519" "crypto/sha256" + "strings" ) const ( HashSize = sha256.Size SignatureSize = ed25519.SignatureSize VerificationKeySize = ed25519.PublicKeySize + + EndpointAddLeaf = Endpoint("add-leaf") + EndpointAddCosignature = Endpoint("add-cosignature") + EndpointGetTreeHeadLatest = Endpoint("get-tree-head-latest") + EndpointGetTreeHeadToSign = Endpoint("get-tree-head-to-sign") + EndpointGetTreeHeadCosigned = Endpoint("get-tree-head-cosigned") + EndpointGetProofByHash = Endpoint("get-proof-by-hash") + EndpointGetConsistencyProof = Endpoint("get-consistency-proof") + EndpointGetLeaves = Endpoint("get-leaves") ) +// Endpoint is a named HTTP API endpoint +type Endpoint string + +// Path joins a number of components to form a full endpoint path. For example, +// EndpointAddLeaf.Path("example.com", "st/v0") -> example.com/st/v0/add-leaf. +func (e Endpoint) Path(components ...string) string { + return strings.Join(append(components, string(e)), "/") +} + // Leaf is the log's Merkle tree leaf. -// -// Ref: https://github.com/system-transparency/stfe/blob/design/doc/api.md#merkle-tree-leaf type Leaf struct { Message SigIdent @@ -35,18 +52,12 @@ type SigIdent struct { // SignedTreeHead is composed of a tree head and a list of signature-signer // pairs. Each signature is computed over the Trunnel-serialized tree head. -// -// Ref: https://github.com/system-transparency/stfe/blob/design/doc/api.md#get-tree-head-cosigned -// Ref: https://github.com/system-transparency/stfe/blob/design/doc/api.md#get-tree-head-to-sign -// Ref: https://github.com/system-transparency/stfe/blob/design/doc/api.md#get-tree-head-latest type SignedTreeHead struct { TreeHead SigIdent []*SigIdent } // TreeHead is the log's tree head. -// -// Ref: https://github.com/system-transparency/stfe/blob/design/doc/api.md#merkle-tree-head type TreeHead struct { Timestamp uint64 TreeSize uint64 @@ -55,8 +66,6 @@ type TreeHead struct { // ConsistencyProof is a consistency proof that proves the log's append-only // property. -// -// Ref: https://github.com/system-transparency/stfe/blob/design/doc/api.md#get-consistency-proof type ConsistencyProof struct { NewSize uint64 OldSize uint64 @@ -65,8 +74,6 @@ type ConsistencyProof struct { // InclusionProof is an inclusion proof that proves a leaf is included in the // log. -// -// Ref: https://github.com/system-transparency/stfe/blob/design/doc/api.md#get-proof-by-hash type InclusionProof struct { TreeSize uint64 LeafIndex uint64 @@ -77,32 +84,24 @@ type InclusionProof struct { type LeafList []*Leaf // ConsistencyProofRequest is a get-consistency-proof request -// -// Ref: https://github.com/system-transparency/stfe/blob/design/doc/api.md#get-consistency-proof type ConsistencyProofRequest struct { NewSize uint64 OldSize uint64 } // InclusionProofRequest is a get-proof-by-hash request -// -// Ref: https://github.com/system-transparency/stfe/blob/design/doc/api.md#get-proof-by-hash type InclusionProofRequest struct { LeafHash *[HashSize]byte TreeSize uint64 } // LeavesRequest is a get-leaves request -// -// Ref: https://github.com/system-transparency/stfe/blob/design/doc/api.md#get-leaves type LeavesRequest struct { StartSize uint64 EndSize uint64 } // LeafRequest is an add-leaf request -// -// Ref: https://github.com/system-transparency/stfe/blob/design/doc/api.md#add-leaf type LeafRequest struct { Message Signature *[SignatureSize]byte @@ -111,8 +110,6 @@ type LeafRequest struct { } // CosignatureRequest is an add-cosignature request -// -// Ref: https://github.com/system-transparency/stfe/blob/design/doc/api.md#add-cosignature type CosignatureRequest struct { SigIdent } diff --git a/types/types_test.go b/types/types_test.go new file mode 100644 index 0000000..9d76c73 --- /dev/null +++ b/types/types_test.go @@ -0,0 +1,53 @@ +package types + +import ( + "testing" +) + +func TestEndpointPath(t *testing.T) { + base, prefix, proto := "example.com", "log", "st/v0" + for _, table := range []struct { + endpoint Endpoint + want string + }{ + { + endpoint: EndpointAddLeaf, + want: "example.com/log/st/v0/add-leaf", + }, + { + endpoint: EndpointAddCosignature, + want: "example.com/log/st/v0/add-cosignature", + }, + { + endpoint: EndpointGetTreeHeadLatest, + want: "example.com/log/st/v0/get-tree-head-latest", + }, + { + endpoint: EndpointGetTreeHeadToSign, + want: "example.com/log/st/v0/get-tree-head-to-sign", + }, + { + endpoint: EndpointGetTreeHeadCosigned, + want: "example.com/log/st/v0/get-tree-head-cosigned", + }, + { + endpoint: EndpointGetConsistencyProof, + want: "example.com/log/st/v0/get-consistency-proof", + }, + { + endpoint: EndpointGetProofByHash, + want: "example.com/log/st/v0/get-proof-by-hash", + }, + { + endpoint: EndpointGetLeaves, + want: "example.com/log/st/v0/get-leaves", + }, + } { + if got, want := table.endpoint.Path(base+"/"+prefix+"/"+proto), table.want; got != want { + t.Errorf("got endpoint\n%s\n\tbut wanted\n%s\n\twith one component", got, want) + } + if got, want := table.endpoint.Path(base, prefix, proto), table.want; got != want { + t.Errorf("got endpoint\n%s\n\tbut wanted\n%s\n\tmultiple components", got, want) + } + } +} -- cgit v1.2.3 From bd7f85f538a14e59efd647c3fadf9e6dbc6383f7 Mon Sep 17 00:00:00 2001 From: Rasmus Dahlberg Date: Wed, 2 Jun 2021 00:40:31 +0200 Subject: added leaf hash helper --- types/util.go | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/types/util.go b/types/util.go index dc8ccba..3cd7dfa 100644 --- a/types/util.go +++ b/types/util.go @@ -4,6 +4,10 @@ import ( "crypto/sha256" ) +const ( + LeafHashPrefix = 0x00 +) + func Hash(buf []byte) *[HashSize]byte { var ret [HashSize]byte hash := sha256.New() @@ -11,3 +15,7 @@ func Hash(buf []byte) *[HashSize]byte { copy(ret[:], hash.Sum(nil)) return &ret } + +func HashLeaf(buf []byte) *[HashSize]byte { + return Hash(append([]byte{LeafHashPrefix}, buf...)) +} -- cgit v1.2.3 From 0d55439c5292ced60e7c2cfd3761f9ef990414ce Mon Sep 17 00:00:00 2001 From: Rasmus Dahlberg Date: Wed, 2 Jun 2021 00:40:55 +0200 Subject: added start on refactored Trillian wrapper --- trillian/client.go | 99 ++++++++++++++++++++++++ trillian/client_test.go | 196 ++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 295 insertions(+) create mode 100644 trillian/client.go create mode 100644 trillian/client_test.go diff --git a/trillian/client.go b/trillian/client.go new file mode 100644 index 0000000..c22e9cc --- /dev/null +++ b/trillian/client.go @@ -0,0 +1,99 @@ +package trillian + +import ( + "context" + "fmt" + + "github.com/golang/glog" + "github.com/google/trillian" + ttypes "github.com/google/trillian/types" + "github.com/system-transparency/stfe/types" + "google.golang.org/grpc/codes" +) + +// Client is a wrapper around the Trillian gRPC client +type Client struct { + // TreeID is a Merkle tree identifier that Trillian uses + TreeID int64 + + // GRPC is a Trillian gRPC client + GRPC trillian.TrillianLogClient +} + +func (c *Client) AddLeaf(ctx context.Context, req *types.LeafRequest) error { + leaf := types.Leaf{ + Message: req.Message, + SigIdent: types.SigIdent{ + Signature: req.Signature, + KeyHash: types.Hash(req.VerificationKey[:]), + }, + } + serialized := leaf.Marshal() + + glog.V(3).Infof("queueing leaf request: %x", types.HashLeaf(serialized)) + rsp, err := c.GRPC.QueueLeaf(ctx, &trillian.QueueLeafRequest{ + LogId: c.TreeID, + Leaf: &trillian.LogLeaf{ + LeafValue: serialized, + }, + }) + if err != nil { + return fmt.Errorf("backend failure: %v", err) + } + if rsp == nil { + return fmt.Errorf("no response") + } + if rsp.QueuedLeaf == nil { + return fmt.Errorf("no queued leaf") + } + if codes.Code(rsp.QueuedLeaf.GetStatus().GetCode()) == codes.AlreadyExists { + return fmt.Errorf("leaf is already queued or included") + } + return nil +} + +func (c *Client) GetTreeHead(ctx context.Context) (*types.TreeHead, error) { + rsp, err := c.GRPC.GetLatestSignedLogRoot(ctx, &trillian.GetLatestSignedLogRootRequest{ + LogId: c.TreeID, + }) + if err != nil { + return nil, fmt.Errorf("backend failure: %v", err) + } + if rsp == nil { + return nil, fmt.Errorf("no response") + } + if rsp.SignedLogRoot == nil { + return nil, fmt.Errorf("no signed log root") + } + if rsp.SignedLogRoot.LogRoot == nil { + return nil, fmt.Errorf("no log root") + } + var r ttypes.LogRootV1 + if err := r.UnmarshalBinary(rsp.SignedLogRoot.LogRoot); err != nil { + return nil, fmt.Errorf("no log root: unmarshal failed: %v", err) + } + if len(r.RootHash) != types.HashSize { + return nil, fmt.Errorf("unexpected hash length: %d", len(r.RootHash)) + } + + var hash [types.HashSize]byte + th := types.TreeHead{ + Timestamp: uint64(r.TimestampNanos / 1000 / 1000 / 1000), + TreeSize: uint64(r.TreeSize), + RootHash: &hash, + } + copy(th.RootHash[:], r.RootHash) + return &th, nil +} + +func (c *Client) GetConsistencyProof(ctx context.Context, req *types.ConsistencyProofRequest) (*types.ConsistencyProof, error) { + return nil, fmt.Errorf("TODO") +} + +func (c *Client) GetInclusionProof(ctx context.Context, req *types.InclusionProofRequest) (*types.InclusionProof, error) { + return nil, fmt.Errorf("TODO") +} + +func (c *Client) GetLeaves(ctx context.Context, req *types.LeavesRequest) (*types.LeafList, error) { + return nil, fmt.Errorf("TODO") +} diff --git a/trillian/client_test.go b/trillian/client_test.go new file mode 100644 index 0000000..1807615 --- /dev/null +++ b/trillian/client_test.go @@ -0,0 +1,196 @@ +package trillian + +import ( + "context" + "fmt" + "reflect" + "testing" + + "github.com/golang/mock/gomock" + "github.com/google/certificate-transparency-go/trillian/mockclient" + "github.com/google/trillian" + ttypes "github.com/google/trillian/types" + "github.com/system-transparency/stfe/types" + "google.golang.org/grpc/codes" + "google.golang.org/grpc/status" +) + +func TestAddLeaf(t *testing.T) { + req := &types.LeafRequest{ + Message: types.Message{ + ShardHint: 0, + Checksum: &[types.HashSize]byte{}, + }, + Signature: &[types.SignatureSize]byte{}, + VerificationKey: &[types.VerificationKeySize]byte{}, + DomainHint: "example.com", + } + for _, table := range []struct { + description string + req *types.LeafRequest + rsp *trillian.QueueLeafResponse + err error + wantErr bool + }{ + { + description: "invalid: backend failure", + req: req, + err: fmt.Errorf("something went wrong"), + wantErr: true, + }, + { + description: "invalid: no response", + req: req, + wantErr: true, + }, + { + description: "invalid: no queued leaf", + req: req, + rsp: &trillian.QueueLeafResponse{}, + wantErr: true, + }, + { + description: "invalid: leaf is already queued or included", + req: req, + rsp: &trillian.QueueLeafResponse{ + QueuedLeaf: &trillian.QueuedLogLeaf{ + Leaf: &trillian.LogLeaf{ + LeafValue: req.Message.Marshal(), + }, + Status: status.New(codes.AlreadyExists, "duplicate").Proto(), + }, + }, + wantErr: true, + }, + { + description: "valid", + req: req, + rsp: &trillian.QueueLeafResponse{ + QueuedLeaf: &trillian.QueuedLogLeaf{ + Leaf: &trillian.LogLeaf{ + LeafValue: req.Message.Marshal(), + }, + Status: status.New(codes.OK, "ok").Proto(), + }, + }, + }, + } { + // Run deferred functions at the end of each iteration + func() { + ctrl := gomock.NewController(t) + defer ctrl.Finish() + grpc := mockclient.NewMockTrillianLogClient(ctrl) + grpc.EXPECT().QueueLeaf(gomock.Any(), gomock.Any()).Return(table.rsp, table.err) + client := Client{GRPC: grpc} + + err := client.AddLeaf(context.Background(), table.req) + if got, want := err != nil, table.wantErr; got != want { + t.Errorf("got error %v but wanted %v in test %q: %v", got, want, table.description, err) + } + }() + } +} + +func TestGetTreeHead(t *testing.T) { + // valid root + root := &ttypes.LogRootV1{ + TreeSize: 0, + RootHash: make([]byte, types.HashSize), + TimestampNanos: 1622585623133599429, + } + buf, err := root.MarshalBinary() + if err != nil { + t.Fatalf("must marshal log root: %v", err) + } + // invalid root + root.RootHash = make([]byte, types.HashSize+1) + bufBadHash, err := root.MarshalBinary() + if err != nil { + t.Fatalf("must marshal log root: %v", err) + } + + for _, table := range []struct { + description string + rsp *trillian.GetLatestSignedLogRootResponse + err error + wantErr bool + wantTh *types.TreeHead + }{ + { + description: "invalid: backend failure", + err: fmt.Errorf("something went wrong"), + wantErr: true, + }, + { + description: "invalid: no response", + wantErr: true, + }, + { + description: "invalid: no signed log root", + rsp: &trillian.GetLatestSignedLogRootResponse{}, + wantErr: true, + }, + { + description: "invalid: no log root", + rsp: &trillian.GetLatestSignedLogRootResponse{ + SignedLogRoot: &trillian.SignedLogRoot{}, + }, + wantErr: true, + }, + { + description: "invalid: no log root: unmarshal failed", + rsp: &trillian.GetLatestSignedLogRootResponse{ + SignedLogRoot: &trillian.SignedLogRoot{ + LogRoot: buf[1:], + }, + }, + wantErr: true, + }, + { + description: "invalid: unexpected hash length", + rsp: &trillian.GetLatestSignedLogRootResponse{ + SignedLogRoot: &trillian.SignedLogRoot{ + LogRoot: bufBadHash, + }, + }, + wantErr: true, + }, + { + description: "valid", + rsp: &trillian.GetLatestSignedLogRootResponse{ + SignedLogRoot: &trillian.SignedLogRoot{ + LogRoot: buf, + }, + }, + wantTh: &types.TreeHead{ + Timestamp: 1622585623, + TreeSize: 0, + RootHash: &[types.HashSize]byte{}, + }, + }, + } { + // Run deferred functions at the end of each iteration + func() { + ctrl := gomock.NewController(t) + defer ctrl.Finish() + grpc := mockclient.NewMockTrillianLogClient(ctrl) + grpc.EXPECT().GetLatestSignedLogRoot(gomock.Any(), gomock.Any()).Return(table.rsp, table.err) + client := Client{GRPC: grpc} + + th, err := client.GetTreeHead(context.Background()) + if got, want := err != nil, table.wantErr; got != want { + t.Errorf("got error %v but wanted %v in test %q: %v", got, want, table.description, err) + } + if err != nil { + return + } + if got, want := th, table.wantTh; !reflect.DeepEqual(got, want) { + t.Errorf("got tree head\n\t%v\nbut wanted\n\t%v\nin test %q", got, want, table.description) + } + }() + } +} + +func TestGetConsistencyProof(t *testing.T) {} +func TestGetInclusionProof(t *testing.T) {} +func TestGetLeaves(t *testing.T) {} -- cgit v1.2.3 From 6e04ae48997ae5fa6e0f803792b674ca24bad8f0 Mon Sep 17 00:00:00 2001 From: Rasmus Dahlberg Date: Wed, 2 Jun 2021 14:39:27 +0200 Subject: added refactored GetConsistencyProof --- trillian/client.go | 37 +++++++++++++----- trillian/client_test.go | 101 ++++++++++++++++++++++++++++++++++++++++++++++-- trillian/util.go | 33 ++++++++++++++++ 3 files changed, 158 insertions(+), 13 deletions(-) create mode 100644 trillian/util.go diff --git a/trillian/client.go b/trillian/client.go index c22e9cc..7b8a00a 100644 --- a/trillian/client.go +++ b/trillian/client.go @@ -75,19 +75,36 @@ func (c *Client) GetTreeHead(ctx context.Context) (*types.TreeHead, error) { if len(r.RootHash) != types.HashSize { return nil, fmt.Errorf("unexpected hash length: %d", len(r.RootHash)) } - - var hash [types.HashSize]byte - th := types.TreeHead{ - Timestamp: uint64(r.TimestampNanos / 1000 / 1000 / 1000), - TreeSize: uint64(r.TreeSize), - RootHash: &hash, - } - copy(th.RootHash[:], r.RootHash) - return &th, nil + return treeHeadFromLogRoot(&r), nil } func (c *Client) GetConsistencyProof(ctx context.Context, req *types.ConsistencyProofRequest) (*types.ConsistencyProof, error) { - return nil, fmt.Errorf("TODO") + rsp, err := c.GRPC.GetConsistencyProof(ctx, &trillian.GetConsistencyProofRequest{ + LogId: c.TreeID, + FirstTreeSize: int64(req.OldSize), + SecondTreeSize: int64(req.NewSize), + }) + if err != nil { + return nil, fmt.Errorf("backend failure: %v", err) + } + if rsp == nil { + return nil, fmt.Errorf("no response") + } + if rsp.Proof == nil { + return nil, fmt.Errorf("no consistency proof") + } + if len(rsp.Proof.Hashes) == 0 { + return nil, fmt.Errorf("not a consistency proof: empty") + } + path, err := nodePathFromHashes(rsp.Proof.Hashes) + if err != nil { + return nil, fmt.Errorf("not a consistency proof: %v", err) + } + return &types.ConsistencyProof{ + OldSize: req.OldSize, + NewSize: req.NewSize, + Path: path, + }, nil } func (c *Client) GetInclusionProof(ctx context.Context, req *types.InclusionProofRequest) (*types.InclusionProof, error) { diff --git a/trillian/client_test.go b/trillian/client_test.go index 1807615..1d1c16f 100644 --- a/trillian/client_test.go +++ b/trillian/client_test.go @@ -191,6 +191,101 @@ func TestGetTreeHead(t *testing.T) { } } -func TestGetConsistencyProof(t *testing.T) {} -func TestGetInclusionProof(t *testing.T) {} -func TestGetLeaves(t *testing.T) {} +func TestGetConsistencyProof(t *testing.T) { + req := &types.ConsistencyProofRequest{ + OldSize: 1, + NewSize: 3, + } + for _, table := range []struct { + description string + req *types.ConsistencyProofRequest + rsp *trillian.GetConsistencyProofResponse + err error + wantErr bool + wantProof *types.ConsistencyProof + }{ + { + description: "invalid: backend failure", + req: req, + err: fmt.Errorf("something went wrong"), + wantErr: true, + }, + { + description: "invalid: no response", + req: req, + wantErr: true, + }, + { + description: "invalid: no consistency proof", + req: req, + rsp: &trillian.GetConsistencyProofResponse{}, + wantErr: true, + }, + { + description: "invalid: not a consistency proof (1/2)", + req: req, + rsp: &trillian.GetConsistencyProofResponse{ + Proof: &trillian.Proof{ + Hashes: [][]byte{}, + }, + }, + wantErr: true, + }, + { + description: "invalid: not a consistency proof (2/2)", + req: req, + rsp: &trillian.GetConsistencyProofResponse{ + Proof: &trillian.Proof{ + Hashes: [][]byte{ + make([]byte, types.HashSize), + make([]byte, types.HashSize+1), + }, + }, + }, + wantErr: true, + }, + { + description: "valid", + req: req, + rsp: &trillian.GetConsistencyProofResponse{ + Proof: &trillian.Proof{ + Hashes: [][]byte{ + make([]byte, types.HashSize), + make([]byte, types.HashSize), + }, + }, + }, + wantProof: &types.ConsistencyProof{ + OldSize: 1, + NewSize: 3, + Path: []*[types.HashSize]byte{ + &[types.HashSize]byte{}, + &[types.HashSize]byte{}, + }, + }, + }, + } { + // Run deferred functions at the end of each iteration + func() { + ctrl := gomock.NewController(t) + defer ctrl.Finish() + grpc := mockclient.NewMockTrillianLogClient(ctrl) + grpc.EXPECT().GetConsistencyProof(gomock.Any(), gomock.Any()).Return(table.rsp, table.err) + client := Client{GRPC: grpc} + + proof, err := client.GetConsistencyProof(context.Background(), table.req) + if got, want := err != nil, table.wantErr; got != want { + t.Errorf("got error %v but wanted %v in test %q: %v", got, want, table.description, err) + } + if err != nil { + return + } + if got, want := proof, table.wantProof; !reflect.DeepEqual(got, want) { + t.Errorf("got proof\n\t%v\nbut wanted\n\t%v\nin test %q", got, want, table.description) + } + }() + } +} + +func TestGetInclusionProof(t *testing.T) {} +func TestGetLeaves(t *testing.T) {} diff --git a/trillian/util.go b/trillian/util.go new file mode 100644 index 0000000..87e64b6 --- /dev/null +++ b/trillian/util.go @@ -0,0 +1,33 @@ +package trillian + +import ( + "fmt" + + trillian "github.com/google/trillian/types" + siglog "github.com/system-transparency/stfe/types" +) + +func treeHeadFromLogRoot(lr *trillian.LogRootV1) *siglog.TreeHead { + var hash [siglog.HashSize]byte + th := siglog.TreeHead{ + Timestamp: uint64(lr.TimestampNanos / 1000 / 1000 / 1000), + TreeSize: uint64(lr.TreeSize), + RootHash: &hash, + } + copy(th.RootHash[:], lr.RootHash) + return &th +} + +func nodePathFromHashes(hashes [][]byte) ([]*[siglog.HashSize]byte, error) { + var path []*[siglog.HashSize]byte + for _, hash := range hashes { + if len(hash) != siglog.HashSize { + return nil, fmt.Errorf("unexpected hash length: %v", len(hash)) + } + + var h [siglog.HashSize]byte + copy(h[:], hash) + path = append(path, &h) + } + return path, nil +} -- cgit v1.2.3 From 0c5cd634e95d0ce07908a8ac32ecf80639f52785 Mon Sep 17 00:00:00 2001 From: Rasmus Dahlberg Date: Wed, 2 Jun 2021 15:01:18 +0200 Subject: added refactored GetInclusionProof --- trillian/client.go | 29 ++++++++++++- trillian/client_test.go | 113 +++++++++++++++++++++++++++++++++++++++++++++++- 2 files changed, 139 insertions(+), 3 deletions(-) diff --git a/trillian/client.go b/trillian/client.go index 7b8a00a..cbeb1ca 100644 --- a/trillian/client.go +++ b/trillian/client.go @@ -108,7 +108,34 @@ func (c *Client) GetConsistencyProof(ctx context.Context, req *types.Consistency } func (c *Client) GetInclusionProof(ctx context.Context, req *types.InclusionProofRequest) (*types.InclusionProof, error) { - return nil, fmt.Errorf("TODO") + rsp, err := c.GRPC.GetInclusionProofByHash(ctx, &trillian.GetInclusionProofByHashRequest{ + LogId: c.TreeID, + LeafHash: req.LeafHash[:], + TreeSize: int64(req.TreeSize), + OrderBySequence: true, + }) + if err != nil { + return nil, fmt.Errorf("backend failure: %v", err) + } + if rsp == nil { + return nil, fmt.Errorf("no response") + } + if len(rsp.Proof) != 1 { + return nil, fmt.Errorf("bad proof count: %d", len(rsp.Proof)) + } + proof := rsp.Proof[0] + if len(proof.Hashes) == 0 { + return nil, fmt.Errorf("not an inclusion proof: empty") + } + path, err := nodePathFromHashes(proof.Hashes) + if err != nil { + return nil, fmt.Errorf("not an inclusion proof: %v", err) + } + return &types.InclusionProof{ + TreeSize: req.TreeSize, + LeafIndex: uint64(proof.LeafIndex), + Path: path, + }, nil } func (c *Client) GetLeaves(ctx context.Context, req *types.LeavesRequest) (*types.LeafList, error) { diff --git a/trillian/client_test.go b/trillian/client_test.go index 1d1c16f..001d1dc 100644 --- a/trillian/client_test.go +++ b/trillian/client_test.go @@ -287,5 +287,114 @@ func TestGetConsistencyProof(t *testing.T) { } } -func TestGetInclusionProof(t *testing.T) {} -func TestGetLeaves(t *testing.T) {} +func TestGetInclusionProof(t *testing.T) { + req := &types.InclusionProofRequest{ + TreeSize: 4, + LeafHash: &[types.HashSize]byte{}, + } + for _, table := range []struct { + description string + req *types.InclusionProofRequest + rsp *trillian.GetInclusionProofByHashResponse + err error + wantErr bool + wantProof *types.InclusionProof + }{ + { + description: "invalid: backend failure", + req: req, + err: fmt.Errorf("something went wrong"), + wantErr: true, + }, + { + description: "invalid: no response", + req: req, + wantErr: true, + }, + { + description: "invalid: bad proof count", + req: req, + rsp: &trillian.GetInclusionProofByHashResponse{ + Proof: []*trillian.Proof{ + &trillian.Proof{}, + &trillian.Proof{}, + }, + }, + wantErr: true, + }, + { + description: "invalid: not an inclusion proof (1/2)", + req: req, + rsp: &trillian.GetInclusionProofByHashResponse{ + Proof: []*trillian.Proof{ + &trillian.Proof{ + LeafIndex: 1, + Hashes: [][]byte{}, + }, + }, + }, + wantErr: true, + }, + { + description: "invalid: not an inclusion proof (2/2)", + req: req, + rsp: &trillian.GetInclusionProofByHashResponse{ + Proof: []*trillian.Proof{ + &trillian.Proof{ + LeafIndex: 1, + Hashes: [][]byte{ + make([]byte, types.HashSize), + make([]byte, types.HashSize+1), + }, + }, + }, + }, + wantErr: true, + }, + { + description: "valid", + req: req, + rsp: &trillian.GetInclusionProofByHashResponse{ + Proof: []*trillian.Proof{ + &trillian.Proof{ + LeafIndex: 1, + Hashes: [][]byte{ + make([]byte, types.HashSize), + make([]byte, types.HashSize), + }, + }, + }, + }, + wantProof: &types.InclusionProof{ + TreeSize: 4, + LeafIndex: 1, + Path: []*[types.HashSize]byte{ + &[types.HashSize]byte{}, + &[types.HashSize]byte{}, + }, + }, + }, + } { + // Run deferred functions at the end of each iteration + func() { + ctrl := gomock.NewController(t) + defer ctrl.Finish() + grpc := mockclient.NewMockTrillianLogClient(ctrl) + grpc.EXPECT().GetInclusionProofByHash(gomock.Any(), gomock.Any()).Return(table.rsp, table.err) + client := Client{GRPC: grpc} + + proof, err := client.GetInclusionProof(context.Background(), table.req) + if got, want := err != nil, table.wantErr; got != want { + t.Errorf("got error %v but wanted %v in test %q: %v", got, want, table.description, err) + } + if err != nil { + return + } + if got, want := proof, table.wantProof; !reflect.DeepEqual(got, want) { + t.Errorf("got proof\n\t%v\nbut wanted\n\t%v\nin test %q", got, want, table.description) + } + }() + } +} + +func TestGetLeaves(t *testing.T) {} -- cgit v1.2.3 From 5d945794190b0088b5a953d4931d8ede31866182 Mon Sep 17 00:00:00 2001 From: Rasmus Dahlberg Date: Wed, 2 Jun 2021 15:42:54 +0200 Subject: added refactored GetInclusionProof --- trillian/client.go | 29 ++++++++++- trillian/client_test.go | 135 +++++++++++++++++++++++++++++++++++++++++++++++- 2 files changed, 162 insertions(+), 2 deletions(-) diff --git a/trillian/client.go b/trillian/client.go index cbeb1ca..c619b03 100644 --- a/trillian/client.go +++ b/trillian/client.go @@ -139,5 +139,32 @@ func (c *Client) GetInclusionProof(ctx context.Context, req *types.InclusionProo } func (c *Client) GetLeaves(ctx context.Context, req *types.LeavesRequest) (*types.LeafList, error) { - return nil, fmt.Errorf("TODO") + rsp, err := c.GRPC.GetLeavesByRange(ctx, &trillian.GetLeavesByRangeRequest{ + LogId: c.TreeID, + StartIndex: int64(req.StartSize), + Count: int64(req.EndSize-req.StartSize) + 1, + }) + if err != nil { + return nil, fmt.Errorf("backend failure: %v", err) + } + if rsp == nil { + return nil, fmt.Errorf("no response") + } + if got, want := len(rsp.Leaves), int(req.EndSize-req.StartSize+1); got != want { + return nil, fmt.Errorf("unexpected number of leaves: %d", got) + } + var list types.LeafList + for i, leaf := range rsp.Leaves { + leafIndex := int64(req.StartSize + uint64(i)) + if leafIndex != leaf.LeafIndex { + return nil, fmt.Errorf("unexpected leaf(%d): got index %d", leafIndex, leaf.LeafIndex) + } + + var l types.Leaf + if err := l.Unmarshal(leaf.LeafValue); err != nil { + return nil, fmt.Errorf("unexpected leaf(%d): %v", leafIndex, err) + } + list = append(list[:], &l) + } + return &list, nil } diff --git a/trillian/client_test.go b/trillian/client_test.go index 001d1dc..bbc61ca 100644 --- a/trillian/client_test.go +++ b/trillian/client_test.go @@ -397,4 +397,137 @@ func TestGetInclusionProof(t *testing.T) { } } -func TestGetLeaves(t *testing.T) {} +func TestGetLeaves(t *testing.T) { + req := &types.LeavesRequest{ + StartSize: 1, + EndSize: 2, + } + firstLeaf := &types.Leaf{ + Message: types.Message{ + ShardHint: 0, + Checksum: &[types.HashSize]byte{}, + }, + SigIdent: types.SigIdent{ + Signature: &[types.SignatureSize]byte{}, + KeyHash: &[types.HashSize]byte{}, + }, + } + secondLeaf := &types.Leaf{ + Message: types.Message{ + ShardHint: 0, + Checksum: &[types.HashSize]byte{}, + }, + SigIdent: types.SigIdent{ + Signature: &[types.SignatureSize]byte{}, + KeyHash: &[types.HashSize]byte{}, + }, + } + + for _, table := range []struct { + description string + req *types.LeavesRequest + rsp *trillian.GetLeavesByRangeResponse + err error + wantErr bool + wantLeaves *types.LeafList + }{ + { + description: "invalid: backend failure", + req: req, + err: fmt.Errorf("something went wrong"), + wantErr: true, + }, + { + description: "invalid: no response", + req: req, + wantErr: true, + }, + { + description: "invalid: unexpected number of leaves", + req: req, + rsp: &trillian.GetLeavesByRangeResponse{ + Leaves: []*trillian.LogLeaf{ + &trillian.LogLeaf{ + LeafValue: firstLeaf.Marshal(), + LeafIndex: 1, + }, + }, + }, + wantErr: true, + }, + { + description: "invalid: unexpected leaf (1/2)", + req: req, + rsp: &trillian.GetLeavesByRangeResponse{ + Leaves: []*trillian.LogLeaf{ + &trillian.LogLeaf{ + LeafValue: firstLeaf.Marshal(), + LeafIndex: 1, + }, + &trillian.LogLeaf{ + LeafValue: secondLeaf.Marshal(), + LeafIndex: 3, + }, + }, + }, + wantErr: true, + }, + { + description: "invalid: unexpected leaf (2/2)", + req: req, + rsp: &trillian.GetLeavesByRangeResponse{ + Leaves: []*trillian.LogLeaf{ + &trillian.LogLeaf{ + LeafValue: firstLeaf.Marshal(), + LeafIndex: 1, + }, + &trillian.LogLeaf{ + LeafValue: secondLeaf.Marshal()[1:], + LeafIndex: 2, + }, + }, + }, + wantErr: true, + }, + { + description: "valid", + req: req, + rsp: &trillian.GetLeavesByRangeResponse{ + Leaves: []*trillian.LogLeaf{ + &trillian.LogLeaf{ + LeafValue: firstLeaf.Marshal(), + LeafIndex: 1, + }, + &trillian.LogLeaf{ + LeafValue: secondLeaf.Marshal(), + LeafIndex: 2, + }, + }, + }, + wantLeaves: &types.LeafList{ + firstLeaf, + secondLeaf, + }, + }, + } { + // Run deferred functions at the end of each iteration + func() { + ctrl := gomock.NewController(t) + defer ctrl.Finish() + grpc := mockclient.NewMockTrillianLogClient(ctrl) + grpc.EXPECT().GetLeavesByRange(gomock.Any(), gomock.Any()).Return(table.rsp, table.err) + client := Client{GRPC: grpc} + + leaves, err := client.GetLeaves(context.Background(), table.req) + if got, want := err != nil, table.wantErr; got != want { + t.Errorf("got error %v but wanted %v in test %q: %v", got, want, table.description, err) + } + if err != nil { + return + } + if got, want := leaves, table.wantLeaves; !reflect.DeepEqual(got, want) { + t.Errorf("got leaves\n\t%v\nbut wanted\n\t%v\nin test %q", got, want, table.description) + } + }() + } +} -- cgit v1.2.3 From 4c7b24401cfda82f61ae87320c72639fc499c32e Mon Sep 17 00:00:00 2001 From: Rasmus Dahlberg Date: Wed, 2 Jun 2021 18:30:55 +0200 Subject: added start on refactored STH source --- state/state_manager.go | 168 ++++++++++++++++++++++++++++++++++++++++++++ state/state_manager_test.go | 26 +++++++ 2 files changed, 194 insertions(+) create mode 100644 state/state_manager.go create mode 100644 state/state_manager_test.go diff --git a/state/state_manager.go b/state/state_manager.go new file mode 100644 index 0000000..21c3eda --- /dev/null +++ b/state/state_manager.go @@ -0,0 +1,168 @@ +package stfe + +import ( + "context" + "crypto" + "crypto/ed25519" + "fmt" + "reflect" + "sync" + "time" + + "github.com/golang/glog" + "github.com/google/certificate-transparency-go/schedule" + "github.com/system-transparency/stfe/trillian" + "github.com/system-transparency/stfe/types" +) + +// StateManager coordinates access to the log's tree heads and (co)signatures +type StateManager interface { + Latest(context.Context) (*types.SignedTreeHead, error) + ToSign(context.Context) (*types.SignedTreeHead, error) + Cosigned(context.Context) (*types.SignedTreeHead, error) + AddCosignature(context.Context, ed25519.PublicKey, *[types.SignatureSize]byte) error + Run(context.Context) +} + +// StateManagerSingle implements the StateManager interface. It is assumed that +// the log server is running on a single-instance machine. So, no coordination. +type StateManagerSingle struct { + client *trillian.Client + signer crypto.Signer + interval time.Duration + deadline time.Duration + sync.RWMutex + + // cosigned is the current cosigned tree head that is being served + cosigned types.SignedTreeHead + + // tosign is the current tree head that is being cosigned by witnesses + tosign types.SignedTreeHead + + // cosignature keeps track of all cosignatures for the tosign tree head + cosignature map[[types.HashSize]byte]*types.SigIdent +} + +func NewStateManagerSingle(client *trillian.Client, signer crypto.Signer, interval, deadline time.Duration) (*StateManagerSingle, error) { + sm := &StateManagerSingle{ + client: client, + signer: signer, + interval: interval, + deadline: deadline, + } + + ctx, _ := context.WithTimeout(context.Background(), sm.deadline) + sth, err := sm.Latest(ctx) + if err != nil { + return nil, fmt.Errorf("Latest: %v", err) + } + + sm.cosigned = *sth + sm.tosign = *sth + sm.cosignature = make(map[[types.HashSize]byte]*types.SigIdent) + return sm, nil +} + +func (sm *StateManagerSingle) Run(ctx context.Context) { + schedule.Every(ctx, sm.interval, func(ctx context.Context) { + ictx, _ := context.WithTimeout(ctx, sm.deadline) + nextTreeHead, err := sm.Latest(ictx) + if err != nil { + glog.Warningf("rotate failed: Latest: %v", err) + return + } + + sm.Lock() + defer sm.Unlock() + sm.rotate(nextTreeHead) + }) +} + +func (sm *StateManagerSingle) Latest(ctx context.Context) (*types.SignedTreeHead, error) { + th, err := sm.client.GetTreeHead(ctx) + if err != nil { + return nil, fmt.Errorf("LatestTreeHead: %v", err) + } + sth, err := sign(sm.signer, th) + if err != nil { + return nil, fmt.Errorf("sign: %v", err) + } + return sth, nil +} + +func (sm *StateManagerSingle) ToSign(_ context.Context) (*types.SignedTreeHead, error) { + sm.RLock() + defer sm.RUnlock() + return &sm.tosign, nil +} + +func (sm *StateManagerSingle) Cosigned(_ context.Context) (*types.SignedTreeHead, error) { + sm.RLock() + defer sm.RUnlock() + return &sm.cosigned, nil +} + +func (sm *StateManagerSingle) AddCosignature(_ context.Context, vk ed25519.PublicKey, sig *[types.SignatureSize]byte) error { + sm.Lock() + defer sm.Unlock() + + if msg := sm.tosign.TreeHead.Marshal(); !ed25519.Verify(vk, msg, sig[:]) { + return fmt.Errorf("invalid signature for tree head with timestamp: %d", sm.tosign.Timestamp) + } + witness := types.Hash(vk[:]) + if _, ok := sm.cosignature[*witness]; ok { + return fmt.Errorf("signature-signer pair is a duplicate") + } + sm.cosignature[*witness] = &types.SigIdent{ + Signature: sig, + KeyHash: witness, + } + + glog.V(3).Infof("accepted new cosignature from witness: %x", *witness) + return nil +} + +// rotate rotates the log's cosigned and stable STH. The caller must aquire the +// source's read-write lock if there are concurrent reads and/or writes. +func (sm *StateManagerSingle) rotate(next *types.SignedTreeHead) { + if reflect.DeepEqual(sm.cosigned.TreeHead, sm.tosign.TreeHead) { + for _, sigident := range sm.cosigned.SigIdent[1:] { // skip log sigident + if _, ok := sm.cosignature[*sigident.KeyHash]; !ok { + sm.cosignature[*sigident.KeyHash] = sigident + } + } + } + // cosignatures will contain all cosignatures (even if repeated tree head) + var cosignatures []*types.SigIdent + for _, sigident := range sm.cosignature { + cosignatures = append(cosignatures, sigident) + } + + // Update cosigned tree head + sm.cosigned.TreeHead = sm.tosign.TreeHead + sm.cosigned.SigIdent = append(sm.tosign.SigIdent, cosignatures...) + + // Update to-sign tree head + sm.tosign = *next + sm.cosignature = make(map[[types.HashSize]byte]*types.SigIdent) + glog.V(3).Infof("rotated sth") +} + +func sign(signer crypto.Signer, th *types.TreeHead) (*types.SignedTreeHead, error) { + sig, err := signer.Sign(nil, th.Marshal(), crypto.Hash(0)) + if err != nil { + return nil, fmt.Errorf("Sign: %v", err) + } + + sigident := types.SigIdent{ + KeyHash: types.Hash(signer.Public().(ed25519.PublicKey)[:]), + Signature: &[types.SignatureSize]byte{}, + } + copy(sigident.Signature[:], sig) + return &types.SignedTreeHead{ + TreeHead: *th, + SigIdent: []*types.SigIdent{ + &sigident, + }, + }, nil +} diff --git a/state/state_manager_test.go b/state/state_manager_test.go new file mode 100644 index 0000000..6db8592 --- /dev/null +++ b/state/state_manager_test.go @@ -0,0 +1,26 @@ +package stfe + +import ( + "testing" +) + +func TestNewStateManagerSingle(t *testing.T) { +} + +func TestLatest(t *testing.T) { +} + +func TestToSign(t *testing.T) { +} + +func TestCosigned(t *testing.T) { +} + +func TestAddCosignature(t *testing.T) { +} + +func TestRotate(t *testing.T) { +} + +func TestSign(t *testing.T) { +} -- cgit v1.2.3 From 38e228dd5aec0bf641dd096acf90aa8c6ad3234c Mon Sep 17 00:00:00 2001 From: Rasmus Dahlberg Date: Thu, 3 Jun 2021 16:32:52 +0200 Subject: added and generated mocks --- trillian/client.go | 22 +++- trillian/client_test.go | 22 ++-- trillian/mocks/stfe.go | 110 ++++++++++++++++ trillian/mocks/trillian.go | 317 +++++++++++++++++++++++++++++++++++++++++++++ 4 files changed, 453 insertions(+), 18 deletions(-) create mode 100644 trillian/mocks/stfe.go create mode 100644 trillian/mocks/trillian.go diff --git a/trillian/client.go b/trillian/client.go index c619b03..9ea6a4a 100644 --- a/trillian/client.go +++ b/trillian/client.go @@ -11,8 +11,16 @@ import ( "google.golang.org/grpc/codes" ) -// Client is a wrapper around the Trillian gRPC client -type Client struct { +type Client interface { + AddLeaf(context.Context, *types.LeafRequest) error + GetConsistencyProof(context.Context, *types.ConsistencyProofRequest) (*types.ConsistencyProof, error) + GetTreeHead(context.Context) (*types.TreeHead, error) + GetInclusionProof(context.Context, *types.InclusionProofRequest) (*types.InclusionProof, error) + GetLeaves(context.Context, *types.LeavesRequest) (*types.LeafList, error) +} + +// TrillianClient is a wrapper around the Trillian gRPC client. +type TrillianClient struct { // TreeID is a Merkle tree identifier that Trillian uses TreeID int64 @@ -20,7 +28,7 @@ type Client struct { GRPC trillian.TrillianLogClient } -func (c *Client) AddLeaf(ctx context.Context, req *types.LeafRequest) error { +func (c *TrillianClient) AddLeaf(ctx context.Context, req *types.LeafRequest) error { leaf := types.Leaf{ Message: req.Message, SigIdent: types.SigIdent{ @@ -52,7 +60,7 @@ func (c *Client) AddLeaf(ctx context.Context, req *types.LeafRequest) error { return nil } -func (c *Client) GetTreeHead(ctx context.Context) (*types.TreeHead, error) { +func (c *TrillianClient) GetTreeHead(ctx context.Context) (*types.TreeHead, error) { rsp, err := c.GRPC.GetLatestSignedLogRoot(ctx, &trillian.GetLatestSignedLogRootRequest{ LogId: c.TreeID, }) @@ -78,7 +86,7 @@ func (c *Client) GetTreeHead(ctx context.Context) (*types.TreeHead, error) { return treeHeadFromLogRoot(&r), nil } -func (c *Client) GetConsistencyProof(ctx context.Context, req *types.ConsistencyProofRequest) (*types.ConsistencyProof, error) { +func (c *TrillianClient) GetConsistencyProof(ctx context.Context, req *types.ConsistencyProofRequest) (*types.ConsistencyProof, error) { rsp, err := c.GRPC.GetConsistencyProof(ctx, &trillian.GetConsistencyProofRequest{ LogId: c.TreeID, FirstTreeSize: int64(req.OldSize), @@ -107,7 +115,7 @@ func (c *Client) GetConsistencyProof(ctx context.Context, req *types.Consistency }, nil } -func (c *Client) GetInclusionProof(ctx context.Context, req *types.InclusionProofRequest) (*types.InclusionProof, error) { +func (c *TrillianClient) GetInclusionProof(ctx context.Context, req *types.InclusionProofRequest) (*types.InclusionProof, error) { rsp, err := c.GRPC.GetInclusionProofByHash(ctx, &trillian.GetInclusionProofByHashRequest{ LogId: c.TreeID, LeafHash: req.LeafHash[:], @@ -138,7 +146,7 @@ func (c *Client) GetInclusionProof(ctx context.Context, req *types.InclusionProo }, nil } -func (c *Client) GetLeaves(ctx context.Context, req *types.LeavesRequest) (*types.LeafList, error) { +func (c *TrillianClient) GetLeaves(ctx context.Context, req *types.LeavesRequest) (*types.LeafList, error) { rsp, err := c.GRPC.GetLeavesByRange(ctx, &trillian.GetLeavesByRangeRequest{ LogId: c.TreeID, StartIndex: int64(req.StartSize), diff --git a/trillian/client_test.go b/trillian/client_test.go index bbc61ca..efb31ba 100644 --- a/trillian/client_test.go +++ b/trillian/client_test.go @@ -7,9 +7,9 @@ import ( "testing" "github.com/golang/mock/gomock" - "github.com/google/certificate-transparency-go/trillian/mockclient" "github.com/google/trillian" ttypes "github.com/google/trillian/types" + "github.com/system-transparency/stfe/trillian/mocks" "github.com/system-transparency/stfe/types" "google.golang.org/grpc/codes" "google.golang.org/grpc/status" @@ -79,9 +79,9 @@ func TestAddLeaf(t *testing.T) { func() { ctrl := gomock.NewController(t) defer ctrl.Finish() - grpc := mockclient.NewMockTrillianLogClient(ctrl) + grpc := mocks.NewMockTrillianLogClient(ctrl) grpc.EXPECT().QueueLeaf(gomock.Any(), gomock.Any()).Return(table.rsp, table.err) - client := Client{GRPC: grpc} + client := TrillianClient{GRPC: grpc} err := client.AddLeaf(context.Background(), table.req) if got, want := err != nil, table.wantErr; got != want { @@ -173,9 +173,9 @@ func TestGetTreeHead(t *testing.T) { func() { ctrl := gomock.NewController(t) defer ctrl.Finish() - grpc := mockclient.NewMockTrillianLogClient(ctrl) + grpc := mocks.NewMockTrillianLogClient(ctrl) grpc.EXPECT().GetLatestSignedLogRoot(gomock.Any(), gomock.Any()).Return(table.rsp, table.err) - client := Client{GRPC: grpc} + client := TrillianClient{GRPC: grpc} th, err := client.GetTreeHead(context.Background()) if got, want := err != nil, table.wantErr; got != want { @@ -269,9 +269,9 @@ func TestGetConsistencyProof(t *testing.T) { func() { ctrl := gomock.NewController(t) defer ctrl.Finish() - grpc := mockclient.NewMockTrillianLogClient(ctrl) + grpc := mocks.NewMockTrillianLogClient(ctrl) grpc.EXPECT().GetConsistencyProof(gomock.Any(), gomock.Any()).Return(table.rsp, table.err) - client := Client{GRPC: grpc} + client := TrillianClient{GRPC: grpc} proof, err := client.GetConsistencyProof(context.Background(), table.req) if got, want := err != nil, table.wantErr; got != want { @@ -379,9 +379,9 @@ func TestGetInclusionProof(t *testing.T) { func() { ctrl := gomock.NewController(t) defer ctrl.Finish() - grpc := mockclient.NewMockTrillianLogClient(ctrl) + grpc := mocks.NewMockTrillianLogClient(ctrl) grpc.EXPECT().GetInclusionProofByHash(gomock.Any(), gomock.Any()).Return(table.rsp, table.err) - client := Client{GRPC: grpc} + client := TrillianClient{GRPC: grpc} proof, err := client.GetInclusionProof(context.Background(), table.req) if got, want := err != nil, table.wantErr; got != want { @@ -514,9 +514,9 @@ func TestGetLeaves(t *testing.T) { func() { ctrl := gomock.NewController(t) defer ctrl.Finish() - grpc := mockclient.NewMockTrillianLogClient(ctrl) + grpc := mocks.NewMockTrillianLogClient(ctrl) grpc.EXPECT().GetLeavesByRange(gomock.Any(), gomock.Any()).Return(table.rsp, table.err) - client := Client{GRPC: grpc} + client := TrillianClient{GRPC: grpc} leaves, err := client.GetLeaves(context.Background(), table.req) if got, want := err != nil, table.wantErr; got != want { diff --git a/trillian/mocks/stfe.go b/trillian/mocks/stfe.go new file mode 100644 index 0000000..e0fe7a9 --- /dev/null +++ b/trillian/mocks/stfe.go @@ -0,0 +1,110 @@ +// Code generated by MockGen. DO NOT EDIT. +// Source: github.com/system-transparency/stfe/trillian (interfaces: Client) + +// Package mocks is a generated GoMock package. +package mocks + +import ( + context "context" + reflect "reflect" + + gomock "github.com/golang/mock/gomock" + types "github.com/system-transparency/stfe/types" +) + +// MockClient is a mock of Client interface. +type MockClient struct { + ctrl *gomock.Controller + recorder *MockClientMockRecorder +} + +// MockClientMockRecorder is the mock recorder for MockClient. +type MockClientMockRecorder struct { + mock *MockClient +} + +// NewMockClient creates a new mock instance. +func NewMockClient(ctrl *gomock.Controller) *MockClient { + mock := &MockClient{ctrl: ctrl} + mock.recorder = &MockClientMockRecorder{mock} + return mock +} + +// EXPECT returns an object that allows the caller to indicate expected use. +func (m *MockClient) EXPECT() *MockClientMockRecorder { + return m.recorder +} + +// AddLeaf mocks base method. +func (m *MockClient) AddLeaf(arg0 context.Context, arg1 *types.LeafRequest) error { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "AddLeaf", arg0, arg1) + ret0, _ := ret[0].(error) + return ret0 +} + +// AddLeaf indicates an expected call of AddLeaf. +func (mr *MockClientMockRecorder) AddLeaf(arg0, arg1 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "AddLeaf", reflect.TypeOf((*MockClient)(nil).AddLeaf), arg0, arg1) +} + +// GetConsistencyProof mocks base method. +func (m *MockClient) GetConsistencyProof(arg0 context.Context, arg1 *types.ConsistencyProofRequest) (*types.ConsistencyProof, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "GetConsistencyProof", arg0, arg1) + ret0, _ := ret[0].(*types.ConsistencyProof) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// GetConsistencyProof indicates an expected call of GetConsistencyProof. +func (mr *MockClientMockRecorder) GetConsistencyProof(arg0, arg1 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetConsistencyProof", reflect.TypeOf((*MockClient)(nil).GetConsistencyProof), arg0, arg1) +} + +// GetInclusionProof mocks base method. +func (m *MockClient) GetInclusionProof(arg0 context.Context, arg1 *types.InclusionProofRequest) (*types.InclusionProof, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "GetInclusionProof", arg0, arg1) + ret0, _ := ret[0].(*types.InclusionProof) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// GetInclusionProof indicates an expected call of GetInclusionProof. +func (mr *MockClientMockRecorder) GetInclusionProof(arg0, arg1 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetInclusionProof", reflect.TypeOf((*MockClient)(nil).GetInclusionProof), arg0, arg1) +} + +// GetLeaves mocks base method. +func (m *MockClient) GetLeaves(arg0 context.Context, arg1 *types.LeavesRequest) (*types.LeafList, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "GetLeaves", arg0, arg1) + ret0, _ := ret[0].(*types.LeafList) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// GetLeaves indicates an expected call of GetLeaves. +func (mr *MockClientMockRecorder) GetLeaves(arg0, arg1 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetLeaves", reflect.TypeOf((*MockClient)(nil).GetLeaves), arg0, arg1) +} + +// GetTreeHead mocks base method. +func (m *MockClient) GetTreeHead(arg0 context.Context) (*types.TreeHead, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "GetTreeHead", arg0) + ret0, _ := ret[0].(*types.TreeHead) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// GetTreeHead indicates an expected call of GetTreeHead. +func (mr *MockClientMockRecorder) GetTreeHead(arg0 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetTreeHead", reflect.TypeOf((*MockClient)(nil).GetTreeHead), arg0) +} diff --git a/trillian/mocks/trillian.go b/trillian/mocks/trillian.go new file mode 100644 index 0000000..8aa3a58 --- /dev/null +++ b/trillian/mocks/trillian.go @@ -0,0 +1,317 @@ +// Code generated by MockGen. DO NOT EDIT. +// Source: github.com/google/trillian (interfaces: TrillianLogClient) + +// Package mocks is a generated GoMock package. +package mocks + +import ( + context "context" + reflect "reflect" + + gomock "github.com/golang/mock/gomock" + trillian "github.com/google/trillian" + grpc "google.golang.org/grpc" +) + +// MockTrillianLogClient is a mock of TrillianLogClient interface. +type MockTrillianLogClient struct { + ctrl *gomock.Controller + recorder *MockTrillianLogClientMockRecorder +} + +// MockTrillianLogClientMockRecorder is the mock recorder for MockTrillianLogClient. +type MockTrillianLogClientMockRecorder struct { + mock *MockTrillianLogClient +} + +// NewMockTrillianLogClient creates a new mock instance. +func NewMockTrillianLogClient(ctrl *gomock.Controller) *MockTrillianLogClient { + mock := &MockTrillianLogClient{ctrl: ctrl} + mock.recorder = &MockTrillianLogClientMockRecorder{mock} + return mock +} + +// EXPECT returns an object that allows the caller to indicate expected use. +func (m *MockTrillianLogClient) EXPECT() *MockTrillianLogClientMockRecorder { + return m.recorder +} + +// AddSequencedLeaf mocks base method. +func (m *MockTrillianLogClient) AddSequencedLeaf(arg0 context.Context, arg1 *trillian.AddSequencedLeafRequest, arg2 ...grpc.CallOption) (*trillian.AddSequencedLeafResponse, error) { + m.ctrl.T.Helper() + varargs := []interface{}{arg0, arg1} + for _, a := range arg2 { + varargs = append(varargs, a) + } + ret := m.ctrl.Call(m, "AddSequencedLeaf", varargs...) + ret0, _ := ret[0].(*trillian.AddSequencedLeafResponse) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// AddSequencedLeaf indicates an expected call of AddSequencedLeaf. +func (mr *MockTrillianLogClientMockRecorder) AddSequencedLeaf(arg0, arg1 interface{}, arg2 ...interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + varargs := append([]interface{}{arg0, arg1}, arg2...) + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "AddSequencedLeaf", reflect.TypeOf((*MockTrillianLogClient)(nil).AddSequencedLeaf), varargs...) +} + +// AddSequencedLeaves mocks base method. +func (m *MockTrillianLogClient) AddSequencedLeaves(arg0 context.Context, arg1 *trillian.AddSequencedLeavesRequest, arg2 ...grpc.CallOption) (*trillian.AddSequencedLeavesResponse, error) { + m.ctrl.T.Helper() + varargs := []interface{}{arg0, arg1} + for _, a := range arg2 { + varargs = append(varargs, a) + } + ret := m.ctrl.Call(m, "AddSequencedLeaves", varargs...) + ret0, _ := ret[0].(*trillian.AddSequencedLeavesResponse) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// AddSequencedLeaves indicates an expected call of AddSequencedLeaves. +func (mr *MockTrillianLogClientMockRecorder) AddSequencedLeaves(arg0, arg1 interface{}, arg2 ...interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + varargs := append([]interface{}{arg0, arg1}, arg2...) + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "AddSequencedLeaves", reflect.TypeOf((*MockTrillianLogClient)(nil).AddSequencedLeaves), varargs...) +} + +// GetConsistencyProof mocks base method. +func (m *MockTrillianLogClient) GetConsistencyProof(arg0 context.Context, arg1 *trillian.GetConsistencyProofRequest, arg2 ...grpc.CallOption) (*trillian.GetConsistencyProofResponse, error) { + m.ctrl.T.Helper() + varargs := []interface{}{arg0, arg1} + for _, a := range arg2 { + varargs = append(varargs, a) + } + ret := m.ctrl.Call(m, "GetConsistencyProof", varargs...) + ret0, _ := ret[0].(*trillian.GetConsistencyProofResponse) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// GetConsistencyProof indicates an expected call of GetConsistencyProof. +func (mr *MockTrillianLogClientMockRecorder) GetConsistencyProof(arg0, arg1 interface{}, arg2 ...interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + varargs := append([]interface{}{arg0, arg1}, arg2...) + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetConsistencyProof", reflect.TypeOf((*MockTrillianLogClient)(nil).GetConsistencyProof), varargs...) +} + +// GetEntryAndProof mocks base method. +func (m *MockTrillianLogClient) GetEntryAndProof(arg0 context.Context, arg1 *trillian.GetEntryAndProofRequest, arg2 ...grpc.CallOption) (*trillian.GetEntryAndProofResponse, error) { + m.ctrl.T.Helper() + varargs := []interface{}{arg0, arg1} + for _, a := range arg2 { + varargs = append(varargs, a) + } + ret := m.ctrl.Call(m, "GetEntryAndProof", varargs...) + ret0, _ := ret[0].(*trillian.GetEntryAndProofResponse) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// GetEntryAndProof indicates an expected call of GetEntryAndProof. +func (mr *MockTrillianLogClientMockRecorder) GetEntryAndProof(arg0, arg1 interface{}, arg2 ...interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + varargs := append([]interface{}{arg0, arg1}, arg2...) + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetEntryAndProof", reflect.TypeOf((*MockTrillianLogClient)(nil).GetEntryAndProof), varargs...) +} + +// GetInclusionProof mocks base method. +func (m *MockTrillianLogClient) GetInclusionProof(arg0 context.Context, arg1 *trillian.GetInclusionProofRequest, arg2 ...grpc.CallOption) (*trillian.GetInclusionProofResponse, error) { + m.ctrl.T.Helper() + varargs := []interface{}{arg0, arg1} + for _, a := range arg2 { + varargs = append(varargs, a) + } + ret := m.ctrl.Call(m, "GetInclusionProof", varargs...) + ret0, _ := ret[0].(*trillian.GetInclusionProofResponse) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// GetInclusionProof indicates an expected call of GetInclusionProof. +func (mr *MockTrillianLogClientMockRecorder) GetInclusionProof(arg0, arg1 interface{}, arg2 ...interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + varargs := append([]interface{}{arg0, arg1}, arg2...) + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetInclusionProof", reflect.TypeOf((*MockTrillianLogClient)(nil).GetInclusionProof), varargs...) +} + +// GetInclusionProofByHash mocks base method. +func (m *MockTrillianLogClient) GetInclusionProofByHash(arg0 context.Context, arg1 *trillian.GetInclusionProofByHashRequest, arg2 ...grpc.CallOption) (*trillian.GetInclusionProofByHashResponse, error) { + m.ctrl.T.Helper() + varargs := []interface{}{arg0, arg1} + for _, a := range arg2 { + varargs = append(varargs, a) + } + ret := m.ctrl.Call(m, "GetInclusionProofByHash", varargs...) + ret0, _ := ret[0].(*trillian.GetInclusionProofByHashResponse) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// GetInclusionProofByHash indicates an expected call of GetInclusionProofByHash. +func (mr *MockTrillianLogClientMockRecorder) GetInclusionProofByHash(arg0, arg1 interface{}, arg2 ...interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + varargs := append([]interface{}{arg0, arg1}, arg2...) + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetInclusionProofByHash", reflect.TypeOf((*MockTrillianLogClient)(nil).GetInclusionProofByHash), varargs...) +} + +// GetLatestSignedLogRoot mocks base method. +func (m *MockTrillianLogClient) GetLatestSignedLogRoot(arg0 context.Context, arg1 *trillian.GetLatestSignedLogRootRequest, arg2 ...grpc.CallOption) (*trillian.GetLatestSignedLogRootResponse, error) { + m.ctrl.T.Helper() + varargs := []interface{}{arg0, arg1} + for _, a := range arg2 { + varargs = append(varargs, a) + } + ret := m.ctrl.Call(m, "GetLatestSignedLogRoot", varargs...) + ret0, _ := ret[0].(*trillian.GetLatestSignedLogRootResponse) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// GetLatestSignedLogRoot indicates an expected call of GetLatestSignedLogRoot. +func (mr *MockTrillianLogClientMockRecorder) GetLatestSignedLogRoot(arg0, arg1 interface{}, arg2 ...interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + varargs := append([]interface{}{arg0, arg1}, arg2...) + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetLatestSignedLogRoot", reflect.TypeOf((*MockTrillianLogClient)(nil).GetLatestSignedLogRoot), varargs...) +} + +// GetLeavesByHash mocks base method. +func (m *MockTrillianLogClient) GetLeavesByHash(arg0 context.Context, arg1 *trillian.GetLeavesByHashRequest, arg2 ...grpc.CallOption) (*trillian.GetLeavesByHashResponse, error) { + m.ctrl.T.Helper() + varargs := []interface{}{arg0, arg1} + for _, a := range arg2 { + varargs = append(varargs, a) + } + ret := m.ctrl.Call(m, "GetLeavesByHash", varargs...) + ret0, _ := ret[0].(*trillian.GetLeavesByHashResponse) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// GetLeavesByHash indicates an expected call of GetLeavesByHash. +func (mr *MockTrillianLogClientMockRecorder) GetLeavesByHash(arg0, arg1 interface{}, arg2 ...interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + varargs := append([]interface{}{arg0, arg1}, arg2...) + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetLeavesByHash", reflect.TypeOf((*MockTrillianLogClient)(nil).GetLeavesByHash), varargs...) +} + +// GetLeavesByIndex mocks base method. +func (m *MockTrillianLogClient) GetLeavesByIndex(arg0 context.Context, arg1 *trillian.GetLeavesByIndexRequest, arg2 ...grpc.CallOption) (*trillian.GetLeavesByIndexResponse, error) { + m.ctrl.T.Helper() + varargs := []interface{}{arg0, arg1} + for _, a := range arg2 { + varargs = append(varargs, a) + } + ret := m.ctrl.Call(m, "GetLeavesByIndex", varargs...) + ret0, _ := ret[0].(*trillian.GetLeavesByIndexResponse) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// GetLeavesByIndex indicates an expected call of GetLeavesByIndex. +func (mr *MockTrillianLogClientMockRecorder) GetLeavesByIndex(arg0, arg1 interface{}, arg2 ...interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + varargs := append([]interface{}{arg0, arg1}, arg2...) + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetLeavesByIndex", reflect.TypeOf((*MockTrillianLogClient)(nil).GetLeavesByIndex), varargs...) +} + +// GetLeavesByRange mocks base method. +func (m *MockTrillianLogClient) GetLeavesByRange(arg0 context.Context, arg1 *trillian.GetLeavesByRangeRequest, arg2 ...grpc.CallOption) (*trillian.GetLeavesByRangeResponse, error) { + m.ctrl.T.Helper() + varargs := []interface{}{arg0, arg1} + for _, a := range arg2 { + varargs = append(varargs, a) + } + ret := m.ctrl.Call(m, "GetLeavesByRange", varargs...) + ret0, _ := ret[0].(*trillian.GetLeavesByRangeResponse) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// GetLeavesByRange indicates an expected call of GetLeavesByRange. +func (mr *MockTrillianLogClientMockRecorder) GetLeavesByRange(arg0, arg1 interface{}, arg2 ...interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + varargs := append([]interface{}{arg0, arg1}, arg2...) + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetLeavesByRange", reflect.TypeOf((*MockTrillianLogClient)(nil).GetLeavesByRange), varargs...) +} + +// GetSequencedLeafCount mocks base method. +func (m *MockTrillianLogClient) GetSequencedLeafCount(arg0 context.Context, arg1 *trillian.GetSequencedLeafCountRequest, arg2 ...grpc.CallOption) (*trillian.GetSequencedLeafCountResponse, error) { + m.ctrl.T.Helper() + varargs := []interface{}{arg0, arg1} + for _, a := range arg2 { + varargs = append(varargs, a) + } + ret := m.ctrl.Call(m, "GetSequencedLeafCount", varargs...) + ret0, _ := ret[0].(*trillian.GetSequencedLeafCountResponse) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// GetSequencedLeafCount indicates an expected call of GetSequencedLeafCount. +func (mr *MockTrillianLogClientMockRecorder) GetSequencedLeafCount(arg0, arg1 interface{}, arg2 ...interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + varargs := append([]interface{}{arg0, arg1}, arg2...) + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetSequencedLeafCount", reflect.TypeOf((*MockTrillianLogClient)(nil).GetSequencedLeafCount), varargs...) +} + +// InitLog mocks base method. +func (m *MockTrillianLogClient) InitLog(arg0 context.Context, arg1 *trillian.InitLogRequest, arg2 ...grpc.CallOption) (*trillian.InitLogResponse, error) { + m.ctrl.T.Helper() + varargs := []interface{}{arg0, arg1} + for _, a := range arg2 { + varargs = append(varargs, a) + } + ret := m.ctrl.Call(m, "InitLog", varargs...) + ret0, _ := ret[0].(*trillian.InitLogResponse) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// InitLog indicates an expected call of InitLog. +func (mr *MockTrillianLogClientMockRecorder) InitLog(arg0, arg1 interface{}, arg2 ...interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + varargs := append([]interface{}{arg0, arg1}, arg2...) + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "InitLog", reflect.TypeOf((*MockTrillianLogClient)(nil).InitLog), varargs...) +} + +// QueueLeaf mocks base method. +func (m *MockTrillianLogClient) QueueLeaf(arg0 context.Context, arg1 *trillian.QueueLeafRequest, arg2 ...grpc.CallOption) (*trillian.QueueLeafResponse, error) { + m.ctrl.T.Helper() + varargs := []interface{}{arg0, arg1} + for _, a := range arg2 { + varargs = append(varargs, a) + } + ret := m.ctrl.Call(m, "QueueLeaf", varargs...) + ret0, _ := ret[0].(*trillian.QueueLeafResponse) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// QueueLeaf indicates an expected call of QueueLeaf. +func (mr *MockTrillianLogClientMockRecorder) QueueLeaf(arg0, arg1 interface{}, arg2 ...interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + varargs := append([]interface{}{arg0, arg1}, arg2...) + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "QueueLeaf", reflect.TypeOf((*MockTrillianLogClient)(nil).QueueLeaf), varargs...) +} + +// QueueLeaves mocks base method. +func (m *MockTrillianLogClient) QueueLeaves(arg0 context.Context, arg1 *trillian.QueueLeavesRequest, arg2 ...grpc.CallOption) (*trillian.QueueLeavesResponse, error) { + m.ctrl.T.Helper() + varargs := []interface{}{arg0, arg1} + for _, a := range arg2 { + varargs = append(varargs, a) + } + ret := m.ctrl.Call(m, "QueueLeaves", varargs...) + ret0, _ := ret[0].(*trillian.QueueLeavesResponse) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// QueueLeaves indicates an expected call of QueueLeaves. +func (mr *MockTrillianLogClientMockRecorder) QueueLeaves(arg0, arg1 interface{}, arg2 ...interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + varargs := append([]interface{}{arg0, arg1}, arg2...) + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "QueueLeaves", reflect.TypeOf((*MockTrillianLogClient)(nil).QueueLeaves), varargs...) +} -- cgit v1.2.3 From 7772c87c837aaf66e39b487b0bc011f3e70bfd60 Mon Sep 17 00:00:00 2001 From: Rasmus Dahlberg Date: Sat, 5 Jun 2021 13:13:50 +0200 Subject: added a simple mock signer --- mocks/crypto.go | 23 +++++++++++++++++++++++ 1 file changed, 23 insertions(+) create mode 100644 mocks/crypto.go diff --git a/mocks/crypto.go b/mocks/crypto.go new file mode 100644 index 0000000..87c883a --- /dev/null +++ b/mocks/crypto.go @@ -0,0 +1,23 @@ +package mocks + +import ( + "crypto" + "crypto/ed25519" + "io" +) + +// TestSign implements the signer interface. It can be used to mock an Ed25519 +// signer that always return the same public key, signature, and error. +type TestSigner struct { + PublicKey *[ed25519.PublicKeySize]byte + Signature *[ed25519.SignatureSize]byte + Error error +} + +func (ts *TestSigner) Public() crypto.PublicKey { + return ed25519.PublicKey(ts.PublicKey[:]) +} + +func (ts *TestSigner) Sign(rand io.Reader, digest []byte, opts crypto.SignerOpts) ([]byte, error) { + return ts.Signature[:], ts.Error +} -- cgit v1.2.3 From 64dc1c02042f7c8bda5faf460ed9e25f4e5574f1 Mon Sep 17 00:00:00 2001 From: Rasmus Dahlberg Date: Sat, 5 Jun 2021 13:14:33 +0200 Subject: moved all mocks to a shared mock directly --- mocks/stfe.go | 110 ++++++++++++++++ mocks/trillian.go | 317 +++++++++++++++++++++++++++++++++++++++++++++ trillian/mocks/stfe.go | 110 ---------------- trillian/mocks/trillian.go | 317 --------------------------------------------- 4 files changed, 427 insertions(+), 427 deletions(-) create mode 100644 mocks/stfe.go create mode 100644 mocks/trillian.go delete mode 100644 trillian/mocks/stfe.go delete mode 100644 trillian/mocks/trillian.go diff --git a/mocks/stfe.go b/mocks/stfe.go new file mode 100644 index 0000000..e0fe7a9 --- /dev/null +++ b/mocks/stfe.go @@ -0,0 +1,110 @@ +// Code generated by MockGen. DO NOT EDIT. +// Source: github.com/system-transparency/stfe/trillian (interfaces: Client) + +// Package mocks is a generated GoMock package. +package mocks + +import ( + context "context" + reflect "reflect" + + gomock "github.com/golang/mock/gomock" + types "github.com/system-transparency/stfe/types" +) + +// MockClient is a mock of Client interface. +type MockClient struct { + ctrl *gomock.Controller + recorder *MockClientMockRecorder +} + +// MockClientMockRecorder is the mock recorder for MockClient. +type MockClientMockRecorder struct { + mock *MockClient +} + +// NewMockClient creates a new mock instance. +func NewMockClient(ctrl *gomock.Controller) *MockClient { + mock := &MockClient{ctrl: ctrl} + mock.recorder = &MockClientMockRecorder{mock} + return mock +} + +// EXPECT returns an object that allows the caller to indicate expected use. +func (m *MockClient) EXPECT() *MockClientMockRecorder { + return m.recorder +} + +// AddLeaf mocks base method. +func (m *MockClient) AddLeaf(arg0 context.Context, arg1 *types.LeafRequest) error { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "AddLeaf", arg0, arg1) + ret0, _ := ret[0].(error) + return ret0 +} + +// AddLeaf indicates an expected call of AddLeaf. +func (mr *MockClientMockRecorder) AddLeaf(arg0, arg1 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "AddLeaf", reflect.TypeOf((*MockClient)(nil).AddLeaf), arg0, arg1) +} + +// GetConsistencyProof mocks base method. +func (m *MockClient) GetConsistencyProof(arg0 context.Context, arg1 *types.ConsistencyProofRequest) (*types.ConsistencyProof, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "GetConsistencyProof", arg0, arg1) + ret0, _ := ret[0].(*types.ConsistencyProof) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// GetConsistencyProof indicates an expected call of GetConsistencyProof. +func (mr *MockClientMockRecorder) GetConsistencyProof(arg0, arg1 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetConsistencyProof", reflect.TypeOf((*MockClient)(nil).GetConsistencyProof), arg0, arg1) +} + +// GetInclusionProof mocks base method. +func (m *MockClient) GetInclusionProof(arg0 context.Context, arg1 *types.InclusionProofRequest) (*types.InclusionProof, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "GetInclusionProof", arg0, arg1) + ret0, _ := ret[0].(*types.InclusionProof) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// GetInclusionProof indicates an expected call of GetInclusionProof. +func (mr *MockClientMockRecorder) GetInclusionProof(arg0, arg1 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetInclusionProof", reflect.TypeOf((*MockClient)(nil).GetInclusionProof), arg0, arg1) +} + +// GetLeaves mocks base method. +func (m *MockClient) GetLeaves(arg0 context.Context, arg1 *types.LeavesRequest) (*types.LeafList, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "GetLeaves", arg0, arg1) + ret0, _ := ret[0].(*types.LeafList) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// GetLeaves indicates an expected call of GetLeaves. +func (mr *MockClientMockRecorder) GetLeaves(arg0, arg1 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetLeaves", reflect.TypeOf((*MockClient)(nil).GetLeaves), arg0, arg1) +} + +// GetTreeHead mocks base method. +func (m *MockClient) GetTreeHead(arg0 context.Context) (*types.TreeHead, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "GetTreeHead", arg0) + ret0, _ := ret[0].(*types.TreeHead) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// GetTreeHead indicates an expected call of GetTreeHead. +func (mr *MockClientMockRecorder) GetTreeHead(arg0 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetTreeHead", reflect.TypeOf((*MockClient)(nil).GetTreeHead), arg0) +} diff --git a/mocks/trillian.go b/mocks/trillian.go new file mode 100644 index 0000000..8aa3a58 --- /dev/null +++ b/mocks/trillian.go @@ -0,0 +1,317 @@ +// Code generated by MockGen. DO NOT EDIT. +// Source: github.com/google/trillian (interfaces: TrillianLogClient) + +// Package mocks is a generated GoMock package. +package mocks + +import ( + context "context" + reflect "reflect" + + gomock "github.com/golang/mock/gomock" + trillian "github.com/google/trillian" + grpc "google.golang.org/grpc" +) + +// MockTrillianLogClient is a mock of TrillianLogClient interface. +type MockTrillianLogClient struct { + ctrl *gomock.Controller + recorder *MockTrillianLogClientMockRecorder +} + +// MockTrillianLogClientMockRecorder is the mock recorder for MockTrillianLogClient. +type MockTrillianLogClientMockRecorder struct { + mock *MockTrillianLogClient +} + +// NewMockTrillianLogClient creates a new mock instance. +func NewMockTrillianLogClient(ctrl *gomock.Controller) *MockTrillianLogClient { + mock := &MockTrillianLogClient{ctrl: ctrl} + mock.recorder = &MockTrillianLogClientMockRecorder{mock} + return mock +} + +// EXPECT returns an object that allows the caller to indicate expected use. +func (m *MockTrillianLogClient) EXPECT() *MockTrillianLogClientMockRecorder { + return m.recorder +} + +// AddSequencedLeaf mocks base method. +func (m *MockTrillianLogClient) AddSequencedLeaf(arg0 context.Context, arg1 *trillian.AddSequencedLeafRequest, arg2 ...grpc.CallOption) (*trillian.AddSequencedLeafResponse, error) { + m.ctrl.T.Helper() + varargs := []interface{}{arg0, arg1} + for _, a := range arg2 { + varargs = append(varargs, a) + } + ret := m.ctrl.Call(m, "AddSequencedLeaf", varargs...) + ret0, _ := ret[0].(*trillian.AddSequencedLeafResponse) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// AddSequencedLeaf indicates an expected call of AddSequencedLeaf. +func (mr *MockTrillianLogClientMockRecorder) AddSequencedLeaf(arg0, arg1 interface{}, arg2 ...interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + varargs := append([]interface{}{arg0, arg1}, arg2...) + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "AddSequencedLeaf", reflect.TypeOf((*MockTrillianLogClient)(nil).AddSequencedLeaf), varargs...) +} + +// AddSequencedLeaves mocks base method. +func (m *MockTrillianLogClient) AddSequencedLeaves(arg0 context.Context, arg1 *trillian.AddSequencedLeavesRequest, arg2 ...grpc.CallOption) (*trillian.AddSequencedLeavesResponse, error) { + m.ctrl.T.Helper() + varargs := []interface{}{arg0, arg1} + for _, a := range arg2 { + varargs = append(varargs, a) + } + ret := m.ctrl.Call(m, "AddSequencedLeaves", varargs...) + ret0, _ := ret[0].(*trillian.AddSequencedLeavesResponse) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// AddSequencedLeaves indicates an expected call of AddSequencedLeaves. +func (mr *MockTrillianLogClientMockRecorder) AddSequencedLeaves(arg0, arg1 interface{}, arg2 ...interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + varargs := append([]interface{}{arg0, arg1}, arg2...) + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "AddSequencedLeaves", reflect.TypeOf((*MockTrillianLogClient)(nil).AddSequencedLeaves), varargs...) +} + +// GetConsistencyProof mocks base method. +func (m *MockTrillianLogClient) GetConsistencyProof(arg0 context.Context, arg1 *trillian.GetConsistencyProofRequest, arg2 ...grpc.CallOption) (*trillian.GetConsistencyProofResponse, error) { + m.ctrl.T.Helper() + varargs := []interface{}{arg0, arg1} + for _, a := range arg2 { + varargs = append(varargs, a) + } + ret := m.ctrl.Call(m, "GetConsistencyProof", varargs...) + ret0, _ := ret[0].(*trillian.GetConsistencyProofResponse) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// GetConsistencyProof indicates an expected call of GetConsistencyProof. +func (mr *MockTrillianLogClientMockRecorder) GetConsistencyProof(arg0, arg1 interface{}, arg2 ...interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + varargs := append([]interface{}{arg0, arg1}, arg2...) + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetConsistencyProof", reflect.TypeOf((*MockTrillianLogClient)(nil).GetConsistencyProof), varargs...) +} + +// GetEntryAndProof mocks base method. +func (m *MockTrillianLogClient) GetEntryAndProof(arg0 context.Context, arg1 *trillian.GetEntryAndProofRequest, arg2 ...grpc.CallOption) (*trillian.GetEntryAndProofResponse, error) { + m.ctrl.T.Helper() + varargs := []interface{}{arg0, arg1} + for _, a := range arg2 { + varargs = append(varargs, a) + } + ret := m.ctrl.Call(m, "GetEntryAndProof", varargs...) + ret0, _ := ret[0].(*trillian.GetEntryAndProofResponse) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// GetEntryAndProof indicates an expected call of GetEntryAndProof. +func (mr *MockTrillianLogClientMockRecorder) GetEntryAndProof(arg0, arg1 interface{}, arg2 ...interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + varargs := append([]interface{}{arg0, arg1}, arg2...) + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetEntryAndProof", reflect.TypeOf((*MockTrillianLogClient)(nil).GetEntryAndProof), varargs...) +} + +// GetInclusionProof mocks base method. +func (m *MockTrillianLogClient) GetInclusionProof(arg0 context.Context, arg1 *trillian.GetInclusionProofRequest, arg2 ...grpc.CallOption) (*trillian.GetInclusionProofResponse, error) { + m.ctrl.T.Helper() + varargs := []interface{}{arg0, arg1} + for _, a := range arg2 { + varargs = append(varargs, a) + } + ret := m.ctrl.Call(m, "GetInclusionProof", varargs...) + ret0, _ := ret[0].(*trillian.GetInclusionProofResponse) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// GetInclusionProof indicates an expected call of GetInclusionProof. +func (mr *MockTrillianLogClientMockRecorder) GetInclusionProof(arg0, arg1 interface{}, arg2 ...interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + varargs := append([]interface{}{arg0, arg1}, arg2...) + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetInclusionProof", reflect.TypeOf((*MockTrillianLogClient)(nil).GetInclusionProof), varargs...) +} + +// GetInclusionProofByHash mocks base method. +func (m *MockTrillianLogClient) GetInclusionProofByHash(arg0 context.Context, arg1 *trillian.GetInclusionProofByHashRequest, arg2 ...grpc.CallOption) (*trillian.GetInclusionProofByHashResponse, error) { + m.ctrl.T.Helper() + varargs := []interface{}{arg0, arg1} + for _, a := range arg2 { + varargs = append(varargs, a) + } + ret := m.ctrl.Call(m, "GetInclusionProofByHash", varargs...) + ret0, _ := ret[0].(*trillian.GetInclusionProofByHashResponse) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// GetInclusionProofByHash indicates an expected call of GetInclusionProofByHash. +func (mr *MockTrillianLogClientMockRecorder) GetInclusionProofByHash(arg0, arg1 interface{}, arg2 ...interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + varargs := append([]interface{}{arg0, arg1}, arg2...) + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetInclusionProofByHash", reflect.TypeOf((*MockTrillianLogClient)(nil).GetInclusionProofByHash), varargs...) +} + +// GetLatestSignedLogRoot mocks base method. +func (m *MockTrillianLogClient) GetLatestSignedLogRoot(arg0 context.Context, arg1 *trillian.GetLatestSignedLogRootRequest, arg2 ...grpc.CallOption) (*trillian.GetLatestSignedLogRootResponse, error) { + m.ctrl.T.Helper() + varargs := []interface{}{arg0, arg1} + for _, a := range arg2 { + varargs = append(varargs, a) + } + ret := m.ctrl.Call(m, "GetLatestSignedLogRoot", varargs...) + ret0, _ := ret[0].(*trillian.GetLatestSignedLogRootResponse) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// GetLatestSignedLogRoot indicates an expected call of GetLatestSignedLogRoot. +func (mr *MockTrillianLogClientMockRecorder) GetLatestSignedLogRoot(arg0, arg1 interface{}, arg2 ...interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + varargs := append([]interface{}{arg0, arg1}, arg2...) + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetLatestSignedLogRoot", reflect.TypeOf((*MockTrillianLogClient)(nil).GetLatestSignedLogRoot), varargs...) +} + +// GetLeavesByHash mocks base method. +func (m *MockTrillianLogClient) GetLeavesByHash(arg0 context.Context, arg1 *trillian.GetLeavesByHashRequest, arg2 ...grpc.CallOption) (*trillian.GetLeavesByHashResponse, error) { + m.ctrl.T.Helper() + varargs := []interface{}{arg0, arg1} + for _, a := range arg2 { + varargs = append(varargs, a) + } + ret := m.ctrl.Call(m, "GetLeavesByHash", varargs...) + ret0, _ := ret[0].(*trillian.GetLeavesByHashResponse) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// GetLeavesByHash indicates an expected call of GetLeavesByHash. +func (mr *MockTrillianLogClientMockRecorder) GetLeavesByHash(arg0, arg1 interface{}, arg2 ...interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + varargs := append([]interface{}{arg0, arg1}, arg2...) + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetLeavesByHash", reflect.TypeOf((*MockTrillianLogClient)(nil).GetLeavesByHash), varargs...) +} + +// GetLeavesByIndex mocks base method. +func (m *MockTrillianLogClient) GetLeavesByIndex(arg0 context.Context, arg1 *trillian.GetLeavesByIndexRequest, arg2 ...grpc.CallOption) (*trillian.GetLeavesByIndexResponse, error) { + m.ctrl.T.Helper() + varargs := []interface{}{arg0, arg1} + for _, a := range arg2 { + varargs = append(varargs, a) + } + ret := m.ctrl.Call(m, "GetLeavesByIndex", varargs...) + ret0, _ := ret[0].(*trillian.GetLeavesByIndexResponse) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// GetLeavesByIndex indicates an expected call of GetLeavesByIndex. +func (mr *MockTrillianLogClientMockRecorder) GetLeavesByIndex(arg0, arg1 interface{}, arg2 ...interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + varargs := append([]interface{}{arg0, arg1}, arg2...) + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetLeavesByIndex", reflect.TypeOf((*MockTrillianLogClient)(nil).GetLeavesByIndex), varargs...) +} + +// GetLeavesByRange mocks base method. +func (m *MockTrillianLogClient) GetLeavesByRange(arg0 context.Context, arg1 *trillian.GetLeavesByRangeRequest, arg2 ...grpc.CallOption) (*trillian.GetLeavesByRangeResponse, error) { + m.ctrl.T.Helper() + varargs := []interface{}{arg0, arg1} + for _, a := range arg2 { + varargs = append(varargs, a) + } + ret := m.ctrl.Call(m, "GetLeavesByRange", varargs...) + ret0, _ := ret[0].(*trillian.GetLeavesByRangeResponse) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// GetLeavesByRange indicates an expected call of GetLeavesByRange. +func (mr *MockTrillianLogClientMockRecorder) GetLeavesByRange(arg0, arg1 interface{}, arg2 ...interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + varargs := append([]interface{}{arg0, arg1}, arg2...) + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetLeavesByRange", reflect.TypeOf((*MockTrillianLogClient)(nil).GetLeavesByRange), varargs...) +} + +// GetSequencedLeafCount mocks base method. +func (m *MockTrillianLogClient) GetSequencedLeafCount(arg0 context.Context, arg1 *trillian.GetSequencedLeafCountRequest, arg2 ...grpc.CallOption) (*trillian.GetSequencedLeafCountResponse, error) { + m.ctrl.T.Helper() + varargs := []interface{}{arg0, arg1} + for _, a := range arg2 { + varargs = append(varargs, a) + } + ret := m.ctrl.Call(m, "GetSequencedLeafCount", varargs...) + ret0, _ := ret[0].(*trillian.GetSequencedLeafCountResponse) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// GetSequencedLeafCount indicates an expected call of GetSequencedLeafCount. +func (mr *MockTrillianLogClientMockRecorder) GetSequencedLeafCount(arg0, arg1 interface{}, arg2 ...interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + varargs := append([]interface{}{arg0, arg1}, arg2...) + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetSequencedLeafCount", reflect.TypeOf((*MockTrillianLogClient)(nil).GetSequencedLeafCount), varargs...) +} + +// InitLog mocks base method. +func (m *MockTrillianLogClient) InitLog(arg0 context.Context, arg1 *trillian.InitLogRequest, arg2 ...grpc.CallOption) (*trillian.InitLogResponse, error) { + m.ctrl.T.Helper() + varargs := []interface{}{arg0, arg1} + for _, a := range arg2 { + varargs = append(varargs, a) + } + ret := m.ctrl.Call(m, "InitLog", varargs...) + ret0, _ := ret[0].(*trillian.InitLogResponse) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// InitLog indicates an expected call of InitLog. +func (mr *MockTrillianLogClientMockRecorder) InitLog(arg0, arg1 interface{}, arg2 ...interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + varargs := append([]interface{}{arg0, arg1}, arg2...) + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "InitLog", reflect.TypeOf((*MockTrillianLogClient)(nil).InitLog), varargs...) +} + +// QueueLeaf mocks base method. +func (m *MockTrillianLogClient) QueueLeaf(arg0 context.Context, arg1 *trillian.QueueLeafRequest, arg2 ...grpc.CallOption) (*trillian.QueueLeafResponse, error) { + m.ctrl.T.Helper() + varargs := []interface{}{arg0, arg1} + for _, a := range arg2 { + varargs = append(varargs, a) + } + ret := m.ctrl.Call(m, "QueueLeaf", varargs...) + ret0, _ := ret[0].(*trillian.QueueLeafResponse) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// QueueLeaf indicates an expected call of QueueLeaf. +func (mr *MockTrillianLogClientMockRecorder) QueueLeaf(arg0, arg1 interface{}, arg2 ...interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + varargs := append([]interface{}{arg0, arg1}, arg2...) + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "QueueLeaf", reflect.TypeOf((*MockTrillianLogClient)(nil).QueueLeaf), varargs...) +} + +// QueueLeaves mocks base method. +func (m *MockTrillianLogClient) QueueLeaves(arg0 context.Context, arg1 *trillian.QueueLeavesRequest, arg2 ...grpc.CallOption) (*trillian.QueueLeavesResponse, error) { + m.ctrl.T.Helper() + varargs := []interface{}{arg0, arg1} + for _, a := range arg2 { + varargs = append(varargs, a) + } + ret := m.ctrl.Call(m, "QueueLeaves", varargs...) + ret0, _ := ret[0].(*trillian.QueueLeavesResponse) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// QueueLeaves indicates an expected call of QueueLeaves. +func (mr *MockTrillianLogClientMockRecorder) QueueLeaves(arg0, arg1 interface{}, arg2 ...interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + varargs := append([]interface{}{arg0, arg1}, arg2...) + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "QueueLeaves", reflect.TypeOf((*MockTrillianLogClient)(nil).QueueLeaves), varargs...) +} diff --git a/trillian/mocks/stfe.go b/trillian/mocks/stfe.go deleted file mode 100644 index e0fe7a9..0000000 --- a/trillian/mocks/stfe.go +++ /dev/null @@ -1,110 +0,0 @@ -// Code generated by MockGen. DO NOT EDIT. -// Source: github.com/system-transparency/stfe/trillian (interfaces: Client) - -// Package mocks is a generated GoMock package. -package mocks - -import ( - context "context" - reflect "reflect" - - gomock "github.com/golang/mock/gomock" - types "github.com/system-transparency/stfe/types" -) - -// MockClient is a mock of Client interface. -type MockClient struct { - ctrl *gomock.Controller - recorder *MockClientMockRecorder -} - -// MockClientMockRecorder is the mock recorder for MockClient. -type MockClientMockRecorder struct { - mock *MockClient -} - -// NewMockClient creates a new mock instance. -func NewMockClient(ctrl *gomock.Controller) *MockClient { - mock := &MockClient{ctrl: ctrl} - mock.recorder = &MockClientMockRecorder{mock} - return mock -} - -// EXPECT returns an object that allows the caller to indicate expected use. -func (m *MockClient) EXPECT() *MockClientMockRecorder { - return m.recorder -} - -// AddLeaf mocks base method. -func (m *MockClient) AddLeaf(arg0 context.Context, arg1 *types.LeafRequest) error { - m.ctrl.T.Helper() - ret := m.ctrl.Call(m, "AddLeaf", arg0, arg1) - ret0, _ := ret[0].(error) - return ret0 -} - -// AddLeaf indicates an expected call of AddLeaf. -func (mr *MockClientMockRecorder) AddLeaf(arg0, arg1 interface{}) *gomock.Call { - mr.mock.ctrl.T.Helper() - return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "AddLeaf", reflect.TypeOf((*MockClient)(nil).AddLeaf), arg0, arg1) -} - -// GetConsistencyProof mocks base method. -func (m *MockClient) GetConsistencyProof(arg0 context.Context, arg1 *types.ConsistencyProofRequest) (*types.ConsistencyProof, error) { - m.ctrl.T.Helper() - ret := m.ctrl.Call(m, "GetConsistencyProof", arg0, arg1) - ret0, _ := ret[0].(*types.ConsistencyProof) - ret1, _ := ret[1].(error) - return ret0, ret1 -} - -// GetConsistencyProof indicates an expected call of GetConsistencyProof. -func (mr *MockClientMockRecorder) GetConsistencyProof(arg0, arg1 interface{}) *gomock.Call { - mr.mock.ctrl.T.Helper() - return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetConsistencyProof", reflect.TypeOf((*MockClient)(nil).GetConsistencyProof), arg0, arg1) -} - -// GetInclusionProof mocks base method. -func (m *MockClient) GetInclusionProof(arg0 context.Context, arg1 *types.InclusionProofRequest) (*types.InclusionProof, error) { - m.ctrl.T.Helper() - ret := m.ctrl.Call(m, "GetInclusionProof", arg0, arg1) - ret0, _ := ret[0].(*types.InclusionProof) - ret1, _ := ret[1].(error) - return ret0, ret1 -} - -// GetInclusionProof indicates an expected call of GetInclusionProof. -func (mr *MockClientMockRecorder) GetInclusionProof(arg0, arg1 interface{}) *gomock.Call { - mr.mock.ctrl.T.Helper() - return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetInclusionProof", reflect.TypeOf((*MockClient)(nil).GetInclusionProof), arg0, arg1) -} - -// GetLeaves mocks base method. -func (m *MockClient) GetLeaves(arg0 context.Context, arg1 *types.LeavesRequest) (*types.LeafList, error) { - m.ctrl.T.Helper() - ret := m.ctrl.Call(m, "GetLeaves", arg0, arg1) - ret0, _ := ret[0].(*types.LeafList) - ret1, _ := ret[1].(error) - return ret0, ret1 -} - -// GetLeaves indicates an expected call of GetLeaves. -func (mr *MockClientMockRecorder) GetLeaves(arg0, arg1 interface{}) *gomock.Call { - mr.mock.ctrl.T.Helper() - return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetLeaves", reflect.TypeOf((*MockClient)(nil).GetLeaves), arg0, arg1) -} - -// GetTreeHead mocks base method. -func (m *MockClient) GetTreeHead(arg0 context.Context) (*types.TreeHead, error) { - m.ctrl.T.Helper() - ret := m.ctrl.Call(m, "GetTreeHead", arg0) - ret0, _ := ret[0].(*types.TreeHead) - ret1, _ := ret[1].(error) - return ret0, ret1 -} - -// GetTreeHead indicates an expected call of GetTreeHead. -func (mr *MockClientMockRecorder) GetTreeHead(arg0 interface{}) *gomock.Call { - mr.mock.ctrl.T.Helper() - return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetTreeHead", reflect.TypeOf((*MockClient)(nil).GetTreeHead), arg0) -} diff --git a/trillian/mocks/trillian.go b/trillian/mocks/trillian.go deleted file mode 100644 index 8aa3a58..0000000 --- a/trillian/mocks/trillian.go +++ /dev/null @@ -1,317 +0,0 @@ -// Code generated by MockGen. DO NOT EDIT. -// Source: github.com/google/trillian (interfaces: TrillianLogClient) - -// Package mocks is a generated GoMock package. -package mocks - -import ( - context "context" - reflect "reflect" - - gomock "github.com/golang/mock/gomock" - trillian "github.com/google/trillian" - grpc "google.golang.org/grpc" -) - -// MockTrillianLogClient is a mock of TrillianLogClient interface. -type MockTrillianLogClient struct { - ctrl *gomock.Controller - recorder *MockTrillianLogClientMockRecorder -} - -// MockTrillianLogClientMockRecorder is the mock recorder for MockTrillianLogClient. -type MockTrillianLogClientMockRecorder struct { - mock *MockTrillianLogClient -} - -// NewMockTrillianLogClient creates a new mock instance. -func NewMockTrillianLogClient(ctrl *gomock.Controller) *MockTrillianLogClient { - mock := &MockTrillianLogClient{ctrl: ctrl} - mock.recorder = &MockTrillianLogClientMockRecorder{mock} - return mock -} - -// EXPECT returns an object that allows the caller to indicate expected use. -func (m *MockTrillianLogClient) EXPECT() *MockTrillianLogClientMockRecorder { - return m.recorder -} - -// AddSequencedLeaf mocks base method. -func (m *MockTrillianLogClient) AddSequencedLeaf(arg0 context.Context, arg1 *trillian.AddSequencedLeafRequest, arg2 ...grpc.CallOption) (*trillian.AddSequencedLeafResponse, error) { - m.ctrl.T.Helper() - varargs := []interface{}{arg0, arg1} - for _, a := range arg2 { - varargs = append(varargs, a) - } - ret := m.ctrl.Call(m, "AddSequencedLeaf", varargs...) - ret0, _ := ret[0].(*trillian.AddSequencedLeafResponse) - ret1, _ := ret[1].(error) - return ret0, ret1 -} - -// AddSequencedLeaf indicates an expected call of AddSequencedLeaf. -func (mr *MockTrillianLogClientMockRecorder) AddSequencedLeaf(arg0, arg1 interface{}, arg2 ...interface{}) *gomock.Call { - mr.mock.ctrl.T.Helper() - varargs := append([]interface{}{arg0, arg1}, arg2...) - return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "AddSequencedLeaf", reflect.TypeOf((*MockTrillianLogClient)(nil).AddSequencedLeaf), varargs...) -} - -// AddSequencedLeaves mocks base method. -func (m *MockTrillianLogClient) AddSequencedLeaves(arg0 context.Context, arg1 *trillian.AddSequencedLeavesRequest, arg2 ...grpc.CallOption) (*trillian.AddSequencedLeavesResponse, error) { - m.ctrl.T.Helper() - varargs := []interface{}{arg0, arg1} - for _, a := range arg2 { - varargs = append(varargs, a) - } - ret := m.ctrl.Call(m, "AddSequencedLeaves", varargs...) - ret0, _ := ret[0].(*trillian.AddSequencedLeavesResponse) - ret1, _ := ret[1].(error) - return ret0, ret1 -} - -// AddSequencedLeaves indicates an expected call of AddSequencedLeaves. -func (mr *MockTrillianLogClientMockRecorder) AddSequencedLeaves(arg0, arg1 interface{}, arg2 ...interface{}) *gomock.Call { - mr.mock.ctrl.T.Helper() - varargs := append([]interface{}{arg0, arg1}, arg2...) - return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "AddSequencedLeaves", reflect.TypeOf((*MockTrillianLogClient)(nil).AddSequencedLeaves), varargs...) -} - -// GetConsistencyProof mocks base method. -func (m *MockTrillianLogClient) GetConsistencyProof(arg0 context.Context, arg1 *trillian.GetConsistencyProofRequest, arg2 ...grpc.CallOption) (*trillian.GetConsistencyProofResponse, error) { - m.ctrl.T.Helper() - varargs := []interface{}{arg0, arg1} - for _, a := range arg2 { - varargs = append(varargs, a) - } - ret := m.ctrl.Call(m, "GetConsistencyProof", varargs...) - ret0, _ := ret[0].(*trillian.GetConsistencyProofResponse) - ret1, _ := ret[1].(error) - return ret0, ret1 -} - -// GetConsistencyProof indicates an expected call of GetConsistencyProof. -func (mr *MockTrillianLogClientMockRecorder) GetConsistencyProof(arg0, arg1 interface{}, arg2 ...interface{}) *gomock.Call { - mr.mock.ctrl.T.Helper() - varargs := append([]interface{}{arg0, arg1}, arg2...) - return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetConsistencyProof", reflect.TypeOf((*MockTrillianLogClient)(nil).GetConsistencyProof), varargs...) -} - -// GetEntryAndProof mocks base method. -func (m *MockTrillianLogClient) GetEntryAndProof(arg0 context.Context, arg1 *trillian.GetEntryAndProofRequest, arg2 ...grpc.CallOption) (*trillian.GetEntryAndProofResponse, error) { - m.ctrl.T.Helper() - varargs := []interface{}{arg0, arg1} - for _, a := range arg2 { - varargs = append(varargs, a) - } - ret := m.ctrl.Call(m, "GetEntryAndProof", varargs...) - ret0, _ := ret[0].(*trillian.GetEntryAndProofResponse) - ret1, _ := ret[1].(error) - return ret0, ret1 -} - -// GetEntryAndProof indicates an expected call of GetEntryAndProof. -func (mr *MockTrillianLogClientMockRecorder) GetEntryAndProof(arg0, arg1 interface{}, arg2 ...interface{}) *gomock.Call { - mr.mock.ctrl.T.Helper() - varargs := append([]interface{}{arg0, arg1}, arg2...) - return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetEntryAndProof", reflect.TypeOf((*MockTrillianLogClient)(nil).GetEntryAndProof), varargs...) -} - -// GetInclusionProof mocks base method. -func (m *MockTrillianLogClient) GetInclusionProof(arg0 context.Context, arg1 *trillian.GetInclusionProofRequest, arg2 ...grpc.CallOption) (*trillian.GetInclusionProofResponse, error) { - m.ctrl.T.Helper() - varargs := []interface{}{arg0, arg1} - for _, a := range arg2 { - varargs = append(varargs, a) - } - ret := m.ctrl.Call(m, "GetInclusionProof", varargs...) - ret0, _ := ret[0].(*trillian.GetInclusionProofResponse) - ret1, _ := ret[1].(error) - return ret0, ret1 -} - -// GetInclusionProof indicates an expected call of GetInclusionProof. -func (mr *MockTrillianLogClientMockRecorder) GetInclusionProof(arg0, arg1 interface{}, arg2 ...interface{}) *gomock.Call { - mr.mock.ctrl.T.Helper() - varargs := append([]interface{}{arg0, arg1}, arg2...) - return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetInclusionProof", reflect.TypeOf((*MockTrillianLogClient)(nil).GetInclusionProof), varargs...) -} - -// GetInclusionProofByHash mocks base method. -func (m *MockTrillianLogClient) GetInclusionProofByHash(arg0 context.Context, arg1 *trillian.GetInclusionProofByHashRequest, arg2 ...grpc.CallOption) (*trillian.GetInclusionProofByHashResponse, error) { - m.ctrl.T.Helper() - varargs := []interface{}{arg0, arg1} - for _, a := range arg2 { - varargs = append(varargs, a) - } - ret := m.ctrl.Call(m, "GetInclusionProofByHash", varargs...) - ret0, _ := ret[0].(*trillian.GetInclusionProofByHashResponse) - ret1, _ := ret[1].(error) - return ret0, ret1 -} - -// GetInclusionProofByHash indicates an expected call of GetInclusionProofByHash. -func (mr *MockTrillianLogClientMockRecorder) GetInclusionProofByHash(arg0, arg1 interface{}, arg2 ...interface{}) *gomock.Call { - mr.mock.ctrl.T.Helper() - varargs := append([]interface{}{arg0, arg1}, arg2...) - return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetInclusionProofByHash", reflect.TypeOf((*MockTrillianLogClient)(nil).GetInclusionProofByHash), varargs...) -} - -// GetLatestSignedLogRoot mocks base method. -func (m *MockTrillianLogClient) GetLatestSignedLogRoot(arg0 context.Context, arg1 *trillian.GetLatestSignedLogRootRequest, arg2 ...grpc.CallOption) (*trillian.GetLatestSignedLogRootResponse, error) { - m.ctrl.T.Helper() - varargs := []interface{}{arg0, arg1} - for _, a := range arg2 { - varargs = append(varargs, a) - } - ret := m.ctrl.Call(m, "GetLatestSignedLogRoot", varargs...) - ret0, _ := ret[0].(*trillian.GetLatestSignedLogRootResponse) - ret1, _ := ret[1].(error) - return ret0, ret1 -} - -// GetLatestSignedLogRoot indicates an expected call of GetLatestSignedLogRoot. -func (mr *MockTrillianLogClientMockRecorder) GetLatestSignedLogRoot(arg0, arg1 interface{}, arg2 ...interface{}) *gomock.Call { - mr.mock.ctrl.T.Helper() - varargs := append([]interface{}{arg0, arg1}, arg2...) - return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetLatestSignedLogRoot", reflect.TypeOf((*MockTrillianLogClient)(nil).GetLatestSignedLogRoot), varargs...) -} - -// GetLeavesByHash mocks base method. -func (m *MockTrillianLogClient) GetLeavesByHash(arg0 context.Context, arg1 *trillian.GetLeavesByHashRequest, arg2 ...grpc.CallOption) (*trillian.GetLeavesByHashResponse, error) { - m.ctrl.T.Helper() - varargs := []interface{}{arg0, arg1} - for _, a := range arg2 { - varargs = append(varargs, a) - } - ret := m.ctrl.Call(m, "GetLeavesByHash", varargs...) - ret0, _ := ret[0].(*trillian.GetLeavesByHashResponse) - ret1, _ := ret[1].(error) - return ret0, ret1 -} - -// GetLeavesByHash indicates an expected call of GetLeavesByHash. -func (mr *MockTrillianLogClientMockRecorder) GetLeavesByHash(arg0, arg1 interface{}, arg2 ...interface{}) *gomock.Call { - mr.mock.ctrl.T.Helper() - varargs := append([]interface{}{arg0, arg1}, arg2...) - return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetLeavesByHash", reflect.TypeOf((*MockTrillianLogClient)(nil).GetLeavesByHash), varargs...) -} - -// GetLeavesByIndex mocks base method. -func (m *MockTrillianLogClient) GetLeavesByIndex(arg0 context.Context, arg1 *trillian.GetLeavesByIndexRequest, arg2 ...grpc.CallOption) (*trillian.GetLeavesByIndexResponse, error) { - m.ctrl.T.Helper() - varargs := []interface{}{arg0, arg1} - for _, a := range arg2 { - varargs = append(varargs, a) - } - ret := m.ctrl.Call(m, "GetLeavesByIndex", varargs...) - ret0, _ := ret[0].(*trillian.GetLeavesByIndexResponse) - ret1, _ := ret[1].(error) - return ret0, ret1 -} - -// GetLeavesByIndex indicates an expected call of GetLeavesByIndex. -func (mr *MockTrillianLogClientMockRecorder) GetLeavesByIndex(arg0, arg1 interface{}, arg2 ...interface{}) *gomock.Call { - mr.mock.ctrl.T.Helper() - varargs := append([]interface{}{arg0, arg1}, arg2...) - return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetLeavesByIndex", reflect.TypeOf((*MockTrillianLogClient)(nil).GetLeavesByIndex), varargs...) -} - -// GetLeavesByRange mocks base method. -func (m *MockTrillianLogClient) GetLeavesByRange(arg0 context.Context, arg1 *trillian.GetLeavesByRangeRequest, arg2 ...grpc.CallOption) (*trillian.GetLeavesByRangeResponse, error) { - m.ctrl.T.Helper() - varargs := []interface{}{arg0, arg1} - for _, a := range arg2 { - varargs = append(varargs, a) - } - ret := m.ctrl.Call(m, "GetLeavesByRange", varargs...) - ret0, _ := ret[0].(*trillian.GetLeavesByRangeResponse) - ret1, _ := ret[1].(error) - return ret0, ret1 -} - -// GetLeavesByRange indicates an expected call of GetLeavesByRange. -func (mr *MockTrillianLogClientMockRecorder) GetLeavesByRange(arg0, arg1 interface{}, arg2 ...interface{}) *gomock.Call { - mr.mock.ctrl.T.Helper() - varargs := append([]interface{}{arg0, arg1}, arg2...) - return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetLeavesByRange", reflect.TypeOf((*MockTrillianLogClient)(nil).GetLeavesByRange), varargs...) -} - -// GetSequencedLeafCount mocks base method. -func (m *MockTrillianLogClient) GetSequencedLeafCount(arg0 context.Context, arg1 *trillian.GetSequencedLeafCountRequest, arg2 ...grpc.CallOption) (*trillian.GetSequencedLeafCountResponse, error) { - m.ctrl.T.Helper() - varargs := []interface{}{arg0, arg1} - for _, a := range arg2 { - varargs = append(varargs, a) - } - ret := m.ctrl.Call(m, "GetSequencedLeafCount", varargs...) - ret0, _ := ret[0].(*trillian.GetSequencedLeafCountResponse) - ret1, _ := ret[1].(error) - return ret0, ret1 -} - -// GetSequencedLeafCount indicates an expected call of GetSequencedLeafCount. -func (mr *MockTrillianLogClientMockRecorder) GetSequencedLeafCount(arg0, arg1 interface{}, arg2 ...interface{}) *gomock.Call { - mr.mock.ctrl.T.Helper() - varargs := append([]interface{}{arg0, arg1}, arg2...) - return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetSequencedLeafCount", reflect.TypeOf((*MockTrillianLogClient)(nil).GetSequencedLeafCount), varargs...) -} - -// InitLog mocks base method. -func (m *MockTrillianLogClient) InitLog(arg0 context.Context, arg1 *trillian.InitLogRequest, arg2 ...grpc.CallOption) (*trillian.InitLogResponse, error) { - m.ctrl.T.Helper() - varargs := []interface{}{arg0, arg1} - for _, a := range arg2 { - varargs = append(varargs, a) - } - ret := m.ctrl.Call(m, "InitLog", varargs...) - ret0, _ := ret[0].(*trillian.InitLogResponse) - ret1, _ := ret[1].(error) - return ret0, ret1 -} - -// InitLog indicates an expected call of InitLog. -func (mr *MockTrillianLogClientMockRecorder) InitLog(arg0, arg1 interface{}, arg2 ...interface{}) *gomock.Call { - mr.mock.ctrl.T.Helper() - varargs := append([]interface{}{arg0, arg1}, arg2...) - return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "InitLog", reflect.TypeOf((*MockTrillianLogClient)(nil).InitLog), varargs...) -} - -// QueueLeaf mocks base method. -func (m *MockTrillianLogClient) QueueLeaf(arg0 context.Context, arg1 *trillian.QueueLeafRequest, arg2 ...grpc.CallOption) (*trillian.QueueLeafResponse, error) { - m.ctrl.T.Helper() - varargs := []interface{}{arg0, arg1} - for _, a := range arg2 { - varargs = append(varargs, a) - } - ret := m.ctrl.Call(m, "QueueLeaf", varargs...) - ret0, _ := ret[0].(*trillian.QueueLeafResponse) - ret1, _ := ret[1].(error) - return ret0, ret1 -} - -// QueueLeaf indicates an expected call of QueueLeaf. -func (mr *MockTrillianLogClientMockRecorder) QueueLeaf(arg0, arg1 interface{}, arg2 ...interface{}) *gomock.Call { - mr.mock.ctrl.T.Helper() - varargs := append([]interface{}{arg0, arg1}, arg2...) - return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "QueueLeaf", reflect.TypeOf((*MockTrillianLogClient)(nil).QueueLeaf), varargs...) -} - -// QueueLeaves mocks base method. -func (m *MockTrillianLogClient) QueueLeaves(arg0 context.Context, arg1 *trillian.QueueLeavesRequest, arg2 ...grpc.CallOption) (*trillian.QueueLeavesResponse, error) { - m.ctrl.T.Helper() - varargs := []interface{}{arg0, arg1} - for _, a := range arg2 { - varargs = append(varargs, a) - } - ret := m.ctrl.Call(m, "QueueLeaves", varargs...) - ret0, _ := ret[0].(*trillian.QueueLeavesResponse) - ret1, _ := ret[1].(error) - return ret0, ret1 -} - -// QueueLeaves indicates an expected call of QueueLeaves. -func (mr *MockTrillianLogClientMockRecorder) QueueLeaves(arg0, arg1 interface{}, arg2 ...interface{}) *gomock.Call { - mr.mock.ctrl.T.Helper() - varargs := append([]interface{}{arg0, arg1}, arg2...) - return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "QueueLeaves", reflect.TypeOf((*MockTrillianLogClient)(nil).QueueLeaves), varargs...) -} -- cgit v1.2.3 From e6ac59fd3d3eca51cc732dd5dc33942be79ffb9b Mon Sep 17 00:00:00 2001 From: Rasmus Dahlberg Date: Sat, 5 Jun 2021 13:15:25 +0200 Subject: fixed mock path --- trillian/client_test.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/trillian/client_test.go b/trillian/client_test.go index efb31ba..e9f1ff5 100644 --- a/trillian/client_test.go +++ b/trillian/client_test.go @@ -9,7 +9,7 @@ import ( "github.com/golang/mock/gomock" "github.com/google/trillian" ttypes "github.com/google/trillian/types" - "github.com/system-transparency/stfe/trillian/mocks" + "github.com/system-transparency/stfe/mocks" "github.com/system-transparency/stfe/types" "google.golang.org/grpc/codes" "google.golang.org/grpc/status" -- cgit v1.2.3 From ebd9efbf32065baa261b2af1625991100ed23bf7 Mon Sep 17 00:00:00 2001 From: Rasmus Dahlberg Date: Sat, 5 Jun 2021 17:15:47 +0200 Subject: attached sign and verify methods to the basic types --- types/types.go | 40 +++++++++++++++++++++++++++++++++++++++- types/types_test.go | 5 +++++ 2 files changed, 44 insertions(+), 1 deletion(-) diff --git a/types/types.go b/types/types.go index d031b29..405c825 100644 --- a/types/types.go +++ b/types/types.go @@ -1,9 +1,12 @@ package types import ( + "crypto" + "fmt" + "strings" + "crypto/ed25519" "crypto/sha256" - "strings" ) const ( @@ -113,3 +116,38 @@ type LeafRequest struct { type CosignatureRequest struct { SigIdent } + +// Sign signs the tree head using the log's signature scheme +func (th *TreeHead) Sign(signer crypto.Signer) (*SignedTreeHead, error) { + sig, err := signer.Sign(nil, th.Marshal(), crypto.Hash(0)) + if err != nil { + return nil, fmt.Errorf("Sign: %v", err) + } + + sigident := SigIdent{ + KeyHash: Hash(signer.Public().(ed25519.PublicKey)[:]), + Signature: &[SignatureSize]byte{}, + } + copy(sigident.Signature[:], sig) + return &SignedTreeHead{ + TreeHead: *th, + SigIdent: []*SigIdent{ + &sigident, + }, + }, nil +} + +// Verify verifies the tree head signature using the log's signature scheme +func (th *TreeHead) Verify(pub crypto.PublicKey) error { // TODO + return nil +} + +// Verify checks if a leaf is included in the log +func (p *InclusionProof) Verify(leaf *Leaf, th *TreeHead) error { // TODO + return nil +} + +// Verify checks if two tree heads are consistent +func (p *ConsistencyProof) Verify(oldTH, newTH *TreeHead) error { // TODO + return nil +} diff --git a/types/types_test.go b/types/types_test.go index 9d76c73..22a03d4 100644 --- a/types/types_test.go +++ b/types/types_test.go @@ -51,3 +51,8 @@ func TestEndpointPath(t *testing.T) { } } } + +func TestTreeHeadSign(t *testing.T) {} +func TestTreeHeadVerify(t *testing.T) {} +func TestInclusionProofVerify(t *testing.T) {} +func TestConsistencyProofVerify(t *testing.T) {} -- cgit v1.2.3 From 0285454c34b0b3003bc8ede3e304b843ad949be8 Mon Sep 17 00:00:00 2001 From: Rasmus Dahlberg Date: Sun, 6 Jun 2021 11:06:17 +0200 Subject: added state manager with tests --- state/state_manager.go | 50 +++--- state/state_manager_test.go | 369 +++++++++++++++++++++++++++++++++++++++++++- types/types.go | 10 +- types/types_test.go | 6 +- 4 files changed, 395 insertions(+), 40 deletions(-) diff --git a/state/state_manager.go b/state/state_manager.go index 21c3eda..3199e61 100644 --- a/state/state_manager.go +++ b/state/state_manager.go @@ -3,7 +3,6 @@ package stfe import ( "context" "crypto" - "crypto/ed25519" "fmt" "reflect" "sync" @@ -20,14 +19,14 @@ type StateManager interface { Latest(context.Context) (*types.SignedTreeHead, error) ToSign(context.Context) (*types.SignedTreeHead, error) Cosigned(context.Context) (*types.SignedTreeHead, error) - AddCosignature(context.Context, ed25519.PublicKey, *[types.SignatureSize]byte) error + AddCosignature(context.Context, *[types.VerificationKeySize]byte, *[types.SignatureSize]byte) error Run(context.Context) } // StateManagerSingle implements the StateManager interface. It is assumed that // the log server is running on a single-instance machine. So, no coordination. type StateManagerSingle struct { - client *trillian.Client + client trillian.Client signer crypto.Signer interval time.Duration deadline time.Duration @@ -43,7 +42,7 @@ type StateManagerSingle struct { cosignature map[[types.HashSize]byte]*types.SigIdent } -func NewStateManagerSingle(client *trillian.Client, signer crypto.Signer, interval, deadline time.Duration) (*StateManagerSingle, error) { +func NewStateManagerSingle(client trillian.Client, signer crypto.Signer, interval, deadline time.Duration) (*StateManagerSingle, error) { sm := &StateManagerSingle{ client: client, signer: signer, @@ -59,7 +58,9 @@ func NewStateManagerSingle(client *trillian.Client, signer crypto.Signer, interv sm.cosigned = *sth sm.tosign = *sth - sm.cosignature = make(map[[types.HashSize]byte]*types.SigIdent) + sm.cosignature = map[[types.HashSize]byte]*types.SigIdent{ + *sth.SigIdent[0].KeyHash: sth.SigIdent[0], // log signature + } return sm, nil } @@ -83,7 +84,7 @@ func (sm *StateManagerSingle) Latest(ctx context.Context) (*types.SignedTreeHead if err != nil { return nil, fmt.Errorf("LatestTreeHead: %v", err) } - sth, err := sign(sm.signer, th) + sth, err := th.Sign(sm.signer) if err != nil { return nil, fmt.Errorf("sign: %v", err) } @@ -102,12 +103,12 @@ func (sm *StateManagerSingle) Cosigned(_ context.Context) (*types.SignedTreeHead return &sm.cosigned, nil } -func (sm *StateManagerSingle) AddCosignature(_ context.Context, vk ed25519.PublicKey, sig *[types.SignatureSize]byte) error { +func (sm *StateManagerSingle) AddCosignature(_ context.Context, vk *[types.VerificationKeySize]byte, sig *[types.SignatureSize]byte) error { sm.Lock() defer sm.Unlock() - if msg := sm.tosign.TreeHead.Marshal(); !ed25519.Verify(vk, msg, sig[:]) { - return fmt.Errorf("invalid signature for tree head with timestamp: %d", sm.tosign.Timestamp) + if err := sm.tosign.TreeHead.Verify(vk, sig); err != nil { + return fmt.Errorf("Verify: %v", err) } witness := types.Hash(vk[:]) if _, ok := sm.cosignature[*witness]; ok { @@ -126,13 +127,15 @@ func (sm *StateManagerSingle) AddCosignature(_ context.Context, vk ed25519.Publi // source's read-write lock if there are concurrent reads and/or writes. func (sm *StateManagerSingle) rotate(next *types.SignedTreeHead) { if reflect.DeepEqual(sm.cosigned.TreeHead, sm.tosign.TreeHead) { - for _, sigident := range sm.cosigned.SigIdent[1:] { // skip log sigident + // cosigned and tosign are the same. So, we need to merge all + // cosignatures that we already had with the new collected ones. + for _, sigident := range sm.cosigned.SigIdent { if _, ok := sm.cosignature[*sigident.KeyHash]; !ok { sm.cosignature[*sigident.KeyHash] = sigident } } + glog.V(3).Infof("cosigned tree head repeated, merged signatures") } - // cosignatures will contain all cosignatures (even if repeated tree head) var cosignatures []*types.SigIdent for _, sigident := range sm.cosignature { cosignatures = append(cosignatures, sigident) @@ -140,29 +143,12 @@ func (sm *StateManagerSingle) rotate(next *types.SignedTreeHead) { // Update cosigned tree head sm.cosigned.TreeHead = sm.tosign.TreeHead - sm.cosigned.SigIdent = append(sm.tosign.SigIdent, cosignatures...) + sm.cosigned.SigIdent = cosignatures // Update to-sign tree head sm.tosign = *next - sm.cosignature = make(map[[types.HashSize]byte]*types.SigIdent) - glog.V(3).Infof("rotated sth") -} - -func sign(signer crypto.Signer, th *types.TreeHead) (*types.SignedTreeHead, error) { - sig, err := signer.Sign(nil, th.Marshal(), crypto.Hash(0)) - if err != nil { - return nil, fmt.Errorf("Sign: %v", err) - } - - sigident := types.SigIdent{ - KeyHash: types.Hash(signer.Public().(ed25519.PublicKey)[:]), - Signature: &[types.SignatureSize]byte{}, + sm.cosignature = map[[types.HashSize]byte]*types.SigIdent{ + *next.SigIdent[0].KeyHash: next.SigIdent[0], // log signature } - copy(sigident.Signature[:], sig) - return &types.SignedTreeHead{ - TreeHead: *th, - SigIdent: []*types.SigIdent{ - &sigident, - }, - }, nil + glog.V(3).Infof("rotated tree heads") } diff --git a/state/state_manager_test.go b/state/state_manager_test.go index 6db8592..348074c 100644 --- a/state/state_manager_test.go +++ b/state/state_manager_test.go @@ -1,26 +1,393 @@ package stfe import ( + "bytes" + "context" + "crypto" + "crypto/ed25519" + "crypto/rand" + "fmt" + "reflect" "testing" + "time" + + "github.com/golang/mock/gomock" + "github.com/system-transparency/stfe/mocks" + "github.com/system-transparency/stfe/types" +) + +var ( + testSig = &[types.SignatureSize]byte{} + testPub = &[types.VerificationKeySize]byte{} + testTH = &types.TreeHead{ + Timestamp: 0, + TreeSize: 0, + RootHash: types.Hash(nil), + } + testSigIdent = &types.SigIdent{ + Signature: testSig, + KeyHash: types.Hash(testPub[:]), + } + testSTH = &types.SignedTreeHead{ + TreeHead: *testTH, + SigIdent: []*types.SigIdent{testSigIdent}, + } + testSignerOK = &mocks.TestSigner{testPub, testSig, nil} + testSignerErr = &mocks.TestSigner{testPub, testSig, fmt.Errorf("something went wrong")} ) func TestNewStateManagerSingle(t *testing.T) { + for _, table := range []struct { + description string + signer crypto.Signer + rsp *types.TreeHead + err error + wantErr bool + wantSth *types.SignedTreeHead + }{ + { + description: "invalid: backend failure", + signer: testSignerOK, + err: fmt.Errorf("something went wrong"), + wantErr: true, + }, + { + description: "valid", + signer: testSignerOK, + rsp: testTH, + wantSth: testSTH, + }, + } { + // Run deferred functions at the end of each iteration + func() { + ctrl := gomock.NewController(t) + defer ctrl.Finish() + client := mocks.NewMockClient(ctrl) + client.EXPECT().GetTreeHead(gomock.Any()).Return(table.rsp, table.err) + + sm, err := NewStateManagerSingle(client, table.signer, time.Duration(0), time.Duration(0)) + if got, want := err != nil, table.wantErr; got != want { + t.Errorf("got error %v but wanted %v in test %q: %v", got, want, table.description, err) + } + if err != nil { + return + } + if got, want := &sm.cosigned, table.wantSth; !reflect.DeepEqual(got, want) { + t.Errorf("got cosigned tree head\n\t%v\nbut wanted\n\t%v\nin test %q", got, want, table.description) + } + if got, want := &sm.tosign, table.wantSth; !reflect.DeepEqual(got, want) { + t.Errorf("got tosign tree head\n\t%v\nbut wanted\n\t%v\nin test %q", got, want, table.description) + } + // we only have log signature on startup + if got, want := len(sm.cosignature), 1; got != want { + t.Errorf("got %d cosignatures but wanted %d in test %q", got, want, table.description) + } + }() + } } func TestLatest(t *testing.T) { + for _, table := range []struct { + description string + signer crypto.Signer + rsp *types.TreeHead + err error + wantErr bool + wantSth *types.SignedTreeHead + }{ + { + description: "invalid: backend failure", + signer: testSignerOK, + err: fmt.Errorf("something went wrong"), + wantErr: true, + }, + { + description: "invalid: signature failure", + rsp: testTH, + signer: testSignerErr, + wantErr: true, + }, + { + description: "valid", + signer: testSignerOK, + rsp: testTH, + wantSth: testSTH, + }, + } { + // Run deferred functions at the end of each iteration + func() { + ctrl := gomock.NewController(t) + defer ctrl.Finish() + client := mocks.NewMockClient(ctrl) + client.EXPECT().GetTreeHead(gomock.Any()).Return(table.rsp, table.err) + sm := StateManagerSingle{ + client: client, + signer: table.signer, + } + + sth, err := sm.Latest(context.Background()) + if got, want := err != nil, table.wantErr; got != want { + t.Errorf("got error %v but wanted %v in test %q: %v", got, want, table.description, err) + } + if err != nil { + return + } + if got, want := sth, table.wantSth; !reflect.DeepEqual(got, want) { + t.Errorf("got signed tree head\n\t%v\nbut wanted\n\t%v\nin test %q", got, want, table.description) + } + }() + } } func TestToSign(t *testing.T) { + description := "valid" + sm := StateManagerSingle{ + tosign: *testSTH, + } + sth, err := sm.ToSign(context.Background()) + if err != nil { + t.Errorf("ToSign should not fail with error: %v", err) + return + } + if got, want := sth, testSTH; !reflect.DeepEqual(got, want) { + t.Errorf("got signed tree head\n\t%v\nbut wanted\n\t%v\nin test %q", got, want, description) + } } func TestCosigned(t *testing.T) { + description := "valid" + sm := StateManagerSingle{ + cosigned: *testSTH, + } + sth, err := sm.Cosigned(context.Background()) + if err != nil { + t.Errorf("Cosigned should not fail with error: %v", err) + return + } + if got, want := sth, testSTH; !reflect.DeepEqual(got, want) { + t.Errorf("got signed tree head\n\t%v\nbut wanted\n\t%v\nin test %q", got, want, description) + } } func TestAddCosignature(t *testing.T) { + vk, sk, err := ed25519.GenerateKey(rand.Reader) + if err != nil { + t.Fatalf("GenerateKey: %v", err) + } + if bytes.Equal(vk[:], testPub[:]) { + t.Fatalf("Sampled same key as testPub, aborting...") + } + var vkArray [types.VerificationKeySize]byte + copy(vkArray[:], vk[:]) + + for _, table := range []struct { + description string + signer crypto.Signer + vk *[types.VerificationKeySize]byte + th *types.TreeHead + wantErr bool + }{ + { + description: "invalid: signature error", + signer: sk, + vk: testPub, // wrong key for message + th: testTH, + wantErr: true, + }, + { + description: "valid", + signer: sk, + vk: &vkArray, + th: testTH, + }, + } { + sth, _ := table.th.Sign(testSignerOK) + logKeyHash := sth.SigIdent[0].KeyHash + logSigIdent := sth.SigIdent[0] + sm := &StateManagerSingle{ + signer: testSignerOK, + cosigned: *sth, + tosign: *sth, + cosignature: map[[types.HashSize]byte]*types.SigIdent{ + *logKeyHash: logSigIdent, + }, + } + + // Prepare witness signature + sth, err := table.th.Sign(table.signer) + if err != nil { + t.Fatalf("Sign: %v", err) + } + witnessKeyHash := sth.SigIdent[0].KeyHash + witnessSigIdent := sth.SigIdent[0] + + // Add witness signature + err = sm.AddCosignature(context.Background(), table.vk, witnessSigIdent.Signature) + if got, want := err != nil, table.wantErr; got != want { + t.Errorf("got error %v but wanted %v in test %q: %v", got, want, table.description, err) + } + if err != nil { + continue + } + + // We should have two signatures (log + witness) + if got, want := len(sm.cosignature), 2; got != want { + t.Errorf("got %d cosignatures but wanted %v in test %q", got, want, table.description) + continue + } + // check that log signature is there + sigident, ok := sm.cosignature[*logKeyHash] + if !ok { + t.Errorf("log signature is missing") + continue + } + if got, want := sigident, logSigIdent; !reflect.DeepEqual(got, want) { + t.Errorf("got log sigident\n\t%v\nbut wanted\n\t%v\nin test %q", got, want, table.description) + } + // check that witness signature is there + sigident, ok = sm.cosignature[*witnessKeyHash] + if !ok { + t.Errorf("witness signature is missing") + continue + } + if got, want := sigident, witnessSigIdent; !reflect.DeepEqual(got, want) { + t.Errorf("got witness sigident\n\t%v\nbut wanted\n\t%v\nin test %q", got, want, table.description) + continue + } + + // Adding a duplicate signature should give an error + if err := sm.AddCosignature(context.Background(), table.vk, witnessSigIdent.Signature); err == nil { + t.Errorf("duplicate witness signature accepted as valid") + } + } } func TestRotate(t *testing.T) { + log := testSigIdent + wit1 := &types.SigIdent{ + Signature: testSig, + KeyHash: types.Hash([]byte("wit1 key")), + } + wit2 := &types.SigIdent{ + Signature: testSig, + KeyHash: types.Hash([]byte("wit2 key")), + } + th0 := testTH + th1 := &types.TreeHead{ + Timestamp: 1, + TreeSize: 1, + RootHash: types.Hash([]byte("1")), + } + th2 := &types.TreeHead{ + Timestamp: 2, + TreeSize: 2, + RootHash: types.Hash([]byte("2")), + } + + for _, table := range []struct { + description string + before, after *StateManagerSingle + next *types.SignedTreeHead + }{ + { + description: "tosign tree head repated, but got one new witnes signature", + before: &StateManagerSingle{ + cosigned: types.SignedTreeHead{ + TreeHead: *th0, + SigIdent: []*types.SigIdent{log, wit1}, + }, + tosign: types.SignedTreeHead{ + TreeHead: *th0, + SigIdent: []*types.SigIdent{log}, + }, + cosignature: map[[types.HashSize]byte]*types.SigIdent{ + *log.KeyHash: log, + *wit2.KeyHash: wit2, // the new witness signature + }, + }, + next: &types.SignedTreeHead{ + TreeHead: *th1, + SigIdent: []*types.SigIdent{log}, + }, + after: &StateManagerSingle{ + cosigned: types.SignedTreeHead{ + TreeHead: *th0, + SigIdent: []*types.SigIdent{log, wit1, wit2}, + }, + tosign: types.SignedTreeHead{ + TreeHead: *th1, + SigIdent: []*types.SigIdent{log}, + }, + cosignature: map[[types.HashSize]byte]*types.SigIdent{ + *log.KeyHash: log, // after rotate we always have log sig + }, + }, + }, + { + description: "tosign tree head did not repeat, it got one witness signature", + before: &StateManagerSingle{ + cosigned: types.SignedTreeHead{ + TreeHead: *th0, + SigIdent: []*types.SigIdent{log, wit1}, + }, + tosign: types.SignedTreeHead{ + TreeHead: *th1, + SigIdent: []*types.SigIdent{log}, + }, + cosignature: map[[types.HashSize]byte]*types.SigIdent{ + *log.KeyHash: log, + *wit2.KeyHash: wit2, // the only witness that signed tosign + }, + }, + next: &types.SignedTreeHead{ + TreeHead: *th2, + SigIdent: []*types.SigIdent{log}, + }, + after: &StateManagerSingle{ + cosigned: types.SignedTreeHead{ + TreeHead: *th1, + SigIdent: []*types.SigIdent{log, wit2}, + }, + tosign: types.SignedTreeHead{ + TreeHead: *th2, + SigIdent: []*types.SigIdent{log}, + }, + cosignature: map[[types.HashSize]byte]*types.SigIdent{ + *log.KeyHash: log, // after rotate we always have log sig + }, + }, + }, + } { + table.before.rotate(table.next) + if got, want := table.before.cosigned.TreeHead, table.after.cosigned.TreeHead; !reflect.DeepEqual(got, want) { + t.Errorf("got cosigned tree head\n\t%v\nbut wanted\n\t%v\nin test %q", got, want, table.description) + } + checkWitnessList(t, table.description, table.before.cosigned.SigIdent, table.after.cosigned.SigIdent) + if got, want := table.before.tosign.TreeHead, table.after.tosign.TreeHead; !reflect.DeepEqual(got, want) { + t.Errorf("got tosign tree head\n\t%v\nbut wanted\n\t%v\nin test %q", got, want, table.description) + } + checkWitnessList(t, table.description, table.before.tosign.SigIdent, table.after.tosign.SigIdent) + if got, want := table.before.cosignature, table.after.cosignature; !reflect.DeepEqual(got, want) { + t.Errorf("got cosignature map\n\t%v\nbut wanted\n\t%v\nin test %q", got, want, table.description) + } + } } -func TestSign(t *testing.T) { +func checkWitnessList(t *testing.T, description string, got, want []*types.SigIdent) { + t.Helper() + for _, si := range got { + found := false + for _, sj := range want { + if reflect.DeepEqual(si, sj) { + found = true + break + } + } + if !found { + t.Errorf("got unexpected signature-signer pair with key hash in test %q: %x", description, si.KeyHash[:]) + } + } + if len(got) != len(want) { + t.Errorf("got %d signature-signer pairs but wanted %d in test %q", len(got), len(want), description) + } } diff --git a/types/types.go b/types/types.go index 405c825..9ca7db8 100644 --- a/types/types.go +++ b/types/types.go @@ -2,11 +2,10 @@ package types import ( "crypto" - "fmt" - "strings" - "crypto/ed25519" "crypto/sha256" + "fmt" + "strings" ) const ( @@ -138,7 +137,10 @@ func (th *TreeHead) Sign(signer crypto.Signer) (*SignedTreeHead, error) { } // Verify verifies the tree head signature using the log's signature scheme -func (th *TreeHead) Verify(pub crypto.PublicKey) error { // TODO +func (th *TreeHead) Verify(vk *[VerificationKeySize]byte, sig *[SignatureSize]byte) error { + if !ed25519.Verify(ed25519.PublicKey(vk[:]), th.Marshal(), sig[:]) { + return fmt.Errorf("invalid tree head signature") + } return nil } diff --git a/types/types_test.go b/types/types_test.go index 22a03d4..da89c59 100644 --- a/types/types_test.go +++ b/types/types_test.go @@ -52,7 +52,7 @@ func TestEndpointPath(t *testing.T) { } } -func TestTreeHeadSign(t *testing.T) {} -func TestTreeHeadVerify(t *testing.T) {} -func TestInclusionProofVerify(t *testing.T) {} +func TestTreeHeadSign(t *testing.T) {} +func TestTreeHeadVerify(t *testing.T) {} +func TestInclusionProofVerify(t *testing.T) {} func TestConsistencyProofVerify(t *testing.T) {} -- cgit v1.2.3 From 47bacfd5c5d22470340e0823c4ad37b45914b68e Mon Sep 17 00:00:00 2001 From: Rasmus Dahlberg Date: Sun, 6 Jun 2021 15:59:35 +0200 Subject: started using the refactored packages in siglog server --- client/client.go | 242 -------------- client/cmd/add-entry/main.go | 52 --- client/cmd/cosign/main.go | 56 ---- client/cmd/example.sh | 49 --- client/cmd/get-consistency-proof/main.go | 70 ---- client/cmd/get-entries/main.go | 83 ----- client/cmd/get-proof-by-hash/main.go | 66 ---- client/cmd/get-sth/main.go | 35 -- client/cmd/keygen/main.go | 17 - client/cmd/submit/main.go | 28 -- client/flag.go | 55 ---- client/verify.go | 52 --- cmd/siglog_server/.gitignore | 1 + cmd/siglog_server/README.md | 60 ++++ cmd/siglog_server/main.go | 176 ++++++++++ cmd/tmp/README.md | 2 + cmd/tmp/cosign/main.go | 56 ++++ cmd/tmp/keygen/main.go | 17 + cmd/tmp/submit/main.go | 29 ++ doc.go | 3 - endpoint.go | 163 ---------- endpoint_test.go | 481 ---------------------------- go.sum | 3 + instance.go | 75 ----- instance_test.go | 159 --------- log_parameters.go | 47 --- log_parameters_test.go | 99 ------ metric.go | 23 -- mocks/crypto.go | 23 -- mocks/stfe.go | 110 ------- mocks/trillian.go | 317 ------------------ pkg/instance/endpoint.go | 122 +++++++ pkg/instance/endpoint_test.go | 480 ++++++++++++++++++++++++++++ pkg/instance/instance.go | 90 ++++++ pkg/instance/instance_test.go | 158 +++++++++ pkg/instance/metric.go | 23 ++ pkg/instance/request.go | 77 +++++ pkg/instance/request_test.go | 318 ++++++++++++++++++ pkg/mocks/crypto.go | 23 ++ pkg/mocks/stfe.go | 110 +++++++ pkg/mocks/trillian.go | 317 ++++++++++++++++++ pkg/state/state_manager.go | 154 +++++++++ pkg/state/state_manager_test.go | 393 +++++++++++++++++++++++ pkg/trillian/client.go | 178 +++++++++++ pkg/trillian/client_test.go | 533 +++++++++++++++++++++++++++++++ pkg/trillian/util.go | 33 ++ pkg/types/ascii.go | 421 ++++++++++++++++++++++++ pkg/types/ascii_test.go | 465 +++++++++++++++++++++++++++ pkg/types/trunnel.go | 60 ++++ pkg/types/trunnel_test.go | 114 +++++++ pkg/types/types.go | 155 +++++++++ pkg/types/types_test.go | 58 ++++ pkg/types/util.go | 21 ++ request.go | 81 ----- request_test.go | 318 ------------------ server/.gitignore | 1 - server/README.md | 60 ---- server/main.go | 165 ---------- state/state_manager.go | 154 --------- state/state_manager_test.go | 393 ----------------------- sth.go | 143 --------- sth_test.go | 466 --------------------------- testdata/data.go | 287 ----------------- trillian.go | 125 -------- trillian/client.go | 178 ----------- trillian/client_test.go | 533 ------------------------------- trillian/util.go | 33 -- trillian_test.go | 282 ---------------- types/ascii.go | 421 ------------------------ types/ascii_test.go | 465 --------------------------- types/trunnel.go | 60 ---- types/trunnel_test.go | 114 ------- types/types.go | 155 --------- types/types_test.go | 58 ---- types/util.go | 21 -- util.go | 27 -- util_test.go | 17 - 77 files changed, 4647 insertions(+), 6862 deletions(-) delete mode 100644 client/client.go delete mode 100644 client/cmd/add-entry/main.go delete mode 100644 client/cmd/cosign/main.go delete mode 100755 client/cmd/example.sh delete mode 100644 client/cmd/get-consistency-proof/main.go delete mode 100644 client/cmd/get-entries/main.go delete mode 100644 client/cmd/get-proof-by-hash/main.go delete mode 100644 client/cmd/get-sth/main.go delete mode 100644 client/cmd/keygen/main.go delete mode 100644 client/cmd/submit/main.go delete mode 100644 client/flag.go delete mode 100644 client/verify.go create mode 100644 cmd/siglog_server/.gitignore create mode 100644 cmd/siglog_server/README.md create mode 100644 cmd/siglog_server/main.go create mode 100644 cmd/tmp/README.md create mode 100644 cmd/tmp/cosign/main.go create mode 100644 cmd/tmp/keygen/main.go create mode 100644 cmd/tmp/submit/main.go delete mode 100644 doc.go delete mode 100644 endpoint.go delete mode 100644 endpoint_test.go delete mode 100644 instance.go delete mode 100644 instance_test.go delete mode 100644 log_parameters.go delete mode 100644 log_parameters_test.go delete mode 100644 metric.go delete mode 100644 mocks/crypto.go delete mode 100644 mocks/stfe.go delete mode 100644 mocks/trillian.go create mode 100644 pkg/instance/endpoint.go create mode 100644 pkg/instance/endpoint_test.go create mode 100644 pkg/instance/instance.go create mode 100644 pkg/instance/instance_test.go create mode 100644 pkg/instance/metric.go create mode 100644 pkg/instance/request.go create mode 100644 pkg/instance/request_test.go create mode 100644 pkg/mocks/crypto.go create mode 100644 pkg/mocks/stfe.go create mode 100644 pkg/mocks/trillian.go create mode 100644 pkg/state/state_manager.go create mode 100644 pkg/state/state_manager_test.go create mode 100644 pkg/trillian/client.go create mode 100644 pkg/trillian/client_test.go create mode 100644 pkg/trillian/util.go create mode 100644 pkg/types/ascii.go create mode 100644 pkg/types/ascii_test.go create mode 100644 pkg/types/trunnel.go create mode 100644 pkg/types/trunnel_test.go create mode 100644 pkg/types/types.go create mode 100644 pkg/types/types_test.go create mode 100644 pkg/types/util.go delete mode 100644 request.go delete mode 100644 request_test.go delete mode 100644 server/.gitignore delete mode 100644 server/README.md delete mode 100644 server/main.go delete mode 100644 state/state_manager.go delete mode 100644 state/state_manager_test.go delete mode 100644 sth.go delete mode 100644 sth_test.go delete mode 100644 testdata/data.go delete mode 100644 trillian.go delete mode 100644 trillian/client.go delete mode 100644 trillian/client_test.go delete mode 100644 trillian/util.go delete mode 100644 trillian_test.go delete mode 100644 types/ascii.go delete mode 100644 types/ascii_test.go delete mode 100644 types/trunnel.go delete mode 100644 types/trunnel_test.go delete mode 100644 types/types.go delete mode 100644 types/types_test.go delete mode 100644 types/util.go delete mode 100644 util.go delete mode 100644 util_test.go diff --git a/client/client.go b/client/client.go deleted file mode 100644 index ba81f4d..0000000 --- a/client/client.go +++ /dev/null @@ -1,242 +0,0 @@ -package client - -import ( - "bytes" - "context" - "crypto" - "fmt" - - "io/ioutil" - "net/http" - - "github.com/golang/glog" - "github.com/google/trillian/merkle/rfc6962" - "github.com/system-transparency/stfe" - "github.com/system-transparency/stfe/types" - "golang.org/x/net/context/ctxhttp" -) - -// Descriptor is a log descriptor -type Descriptor struct { - Namespace *types.Namespace // log identifier is a namespace - Url string // log url, e.g., http://example.com/st/v1 -} - -// Client is a log client -type Client struct { - HttpClient *http.Client - Signer crypto.Signer // client's private identity - Namespace *types.Namespace // client's public identity - Log *Descriptor // log's public identity -} - -// GetLatestSth fetches and verifies the signature of the most recent STH. -// Outputs the resulting STH. -func (c *Client) GetLatestSth(ctx context.Context) (*types.StItem, error) { - url := stfe.EndpointGetLatestSth.Path(c.Log.Url) - req, err := http.NewRequest("GET", url, nil) - if err != nil { - return nil, fmt.Errorf("failed creating http request: %v", err) - } - glog.V(3).Infof("created http request: %s %s", req.Method, req.URL) - - item, err := c.doRequestWithStItemResponse(ctx, req) - if err != nil { - return nil, err - } - if got, want := item.Format, types.StFormatSignedTreeHeadV1; got != want { - return nil, fmt.Errorf("unexpected StItem format: %v", got) - } - if err := VerifySignedTreeHeadV1(c.Log.Namespace, item); err != nil { - return nil, fmt.Errorf("signature verification failed: %v", err) - } - glog.V(3).Infof("verified sth") - return item, nil -} - -// GetProofByHash fetches and verifies an inclusion proof for a leaf hash -// against an STH. Outputs the resulting proof. -func (c *Client) GetProofByHash(ctx context.Context, leafHash []byte, sth *types.StItem) (*types.StItem, error) { - if err := VerifySignedTreeHeadV1(c.Log.Namespace, sth); err != nil { - return nil, fmt.Errorf("invalid sth: %v", err) - } - glog.V(3).Infof("verified sth") - params := types.GetProofByHashV1{ - TreeSize: sth.SignedTreeHeadV1.TreeHead.TreeSize, - } - copy(params.Hash[:], leafHash) - buf, err := types.Marshal(params) - if err != nil { - return nil, fmt.Errorf("req: Marshal: %v", err) - } - - url := stfe.EndpointGetProofByHash.Path(c.Log.Url) - req, err := http.NewRequest("POST", url, bytes.NewBuffer(buf)) - if err != nil { - return nil, fmt.Errorf("failed creating http request: %v", err) - } - req.Header.Set("Content-Type", "application/octet-stream") - glog.V(3).Infof("created http request: %s %s", req.Method, req.URL) - - item, err := c.doRequestWithStItemResponse(ctx, req) - if err != nil { - return nil, fmt.Errorf("doRequestWithStItemResponse: %v", err) - } - if got, want := item.Format, types.StFormatInclusionProofV1; got != want { - return nil, fmt.Errorf("unexpected StItem format: %v", item.Format) - } - if err := VerifyInclusionProofV1(item, sth, params.Hash[:]); err != nil { - return nil, fmt.Errorf("invalid inclusion proof: %v", err) - } - glog.V(3).Infof("verified inclusion proof") - return item, nil -} - -// GetConsistencyProof fetches and verifies a consistency proof betweeen two -// STHs. Outputs the resulting proof. -func (c *Client) GetConsistencyProof(ctx context.Context, sth1, sth2 *types.StItem) (*types.StItem, error) { - if err := VerifySignedTreeHeadV1(c.Log.Namespace, sth1); err != nil { - return nil, fmt.Errorf("invalid first sth: %v", err) - } - if err := VerifySignedTreeHeadV1(c.Log.Namespace, sth2); err != nil { - return nil, fmt.Errorf("invalid second sth: %v", err) - } - glog.V(3).Infof("verified sths") - buf, err := types.Marshal(types.GetConsistencyProofV1{ - First: sth1.SignedTreeHeadV1.TreeHead.TreeSize, - Second: sth2.SignedTreeHeadV1.TreeHead.TreeSize, - }) - if err != nil { - return nil, fmt.Errorf("req: Marshal: %v", err) - } - - url := stfe.EndpointGetConsistencyProof.Path(c.Log.Url) - req, err := http.NewRequest("POST", url, bytes.NewBuffer(buf)) - if err != nil { - return nil, fmt.Errorf("failed creating http request: %v", err) - } - req.Header.Set("Content-Type", "application/octet-stream") - glog.V(3).Infof("created http request: %s %s", req.Method, req.URL) - - item, err := c.doRequestWithStItemResponse(ctx, req) - if err != nil { - return nil, fmt.Errorf("doRequestWithStItemResponse: %v", err) - } - if got, want := item.Format, types.StFormatConsistencyProofV1; got != want { - return nil, fmt.Errorf("unexpected StItem format: %v", item.Format) - } - if err := VerifyConsistencyProofV1(item, sth1, sth2); err != nil { - return nil, fmt.Errorf("invalid inclusion proof: %v", err) - } - glog.V(3).Infof("verified inclusion proof") - return item, nil -} - -// AddEntry signs and submits a checksum_v1 entry to the log. Outputs the -// resulting leaf-hash on success. -func (c *Client) AddEntry(ctx context.Context, data *types.ChecksumV1) ([]byte, error) { - msg, err := types.Marshal(*data) - if err != nil { - return nil, fmt.Errorf("failed marshaling ChecksumV1: %v", err) - } - sig, err := c.Signer.Sign(nil, msg, crypto.Hash(0)) - if err != nil { - return nil, fmt.Errorf("failed signing ChecksumV1: %v", err) - } - leaf, err := types.Marshal(*types.NewSignedChecksumV1(data, &types.SignatureV1{ - Namespace: *c.Namespace, - Signature: sig, - })) - if err != nil { - return nil, fmt.Errorf("failed marshaling SignedChecksumV1: %v", err) - } - glog.V(3).Infof("signed checksum entry for identifier %q", string(data.Identifier)) - - url := stfe.EndpointAddEntry.Path(c.Log.Url) - req, err := http.NewRequest("POST", url, bytes.NewBuffer(leaf)) - if err != nil { - return nil, fmt.Errorf("failed creating http request: %v", err) - } - req.Header.Set("Content-Type", "application/octet-stream") - glog.V(3).Infof("created http request: %s %s", req.Method, req.URL) - - if rsp, err := c.doRequest(ctx, req); err != nil { - return nil, fmt.Errorf("doRequest: %v", err) - } else if len(rsp) != 0 { - return nil, fmt.Errorf("extra data: %v", err) - } - glog.V(3).Infof("add-entry succeded") - return rfc6962.DefaultHasher.HashLeaf(leaf), nil -} - -// GetEntries fetches a range of entries from the log, verifying that they are -// of type signed_checksum_v1 but nothing more than that. Outputs the resulting -// range that may be truncated by the log if [start,end] is too large. -func (c *Client) GetEntries(ctx context.Context, start, end uint64) ([]*types.StItem, error) { - buf, err := types.Marshal(types.GetEntriesV1{ - Start: start, - End: end, - }) - if err != nil { - return nil, fmt.Errorf("Marshal: %v", err) - } - url := stfe.EndpointGetEntries.Path(c.Log.Url) - req, err := http.NewRequest("POST", url, bytes.NewBuffer(buf)) - if err != nil { - return nil, fmt.Errorf("failed creating http request: %v", err) - } - req.Header.Set("Content-Type", "application/octet-stream") - glog.V(3).Infof("created http request: %s %s", req.Method, req.URL) - glog.V(3).Infof("request data: start(%d), end(%d)", start, end) - - body, err := c.doRequest(ctx, req) - if err != nil { - return nil, fmt.Errorf("doRequest: %v", err) - } - var list types.StItemList - if err := types.Unmarshal(body, &list); err != nil { - return nil, fmt.Errorf("Unmarshal: %v", err) - } - ret := make([]*types.StItem, 0, len(list.Items)) - for i, _ := range list.Items { - item := list.Items[i] - if got, want := item.Format, types.StFormatSignedChecksumV1; got != want { - return nil, fmt.Errorf("unexpected StItem format: %v", got) - } - ret = append(ret, &item) - } - return ret, nil -} - -// doRequest sends an HTTP request and outputs the raw body -func (c *Client) doRequest(ctx context.Context, req *http.Request) ([]byte, error) { - rsp, err := ctxhttp.Do(ctx, c.HttpClient, req) - if err != nil { - return nil, fmt.Errorf("no response: %v", err) - } - defer rsp.Body.Close() - if got, want := rsp.StatusCode, http.StatusOK; got != want { - return nil, fmt.Errorf("bad http status: %v", got) - } - body, err := ioutil.ReadAll(rsp.Body) - if err != nil { - return nil, fmt.Errorf("cannot read body: %v", err) - } - return body, nil -} - -// -// doRequestWithStItemResponse sends an HTTP request and returns a decoded -// StItem that the resulting HTTP response contained json:ed and marshaled -func (c *Client) doRequestWithStItemResponse(ctx context.Context, req *http.Request) (*types.StItem, error) { - body, err := c.doRequest(ctx, req) - if err != nil { - return nil, err - } - var item types.StItem - if err := types.Unmarshal(body, &item); err != nil { - return nil, fmt.Errorf("failed decoding StItem: %v", err) - } - glog.V(9).Infof("got StItem: %v", item) - return &item, nil -} diff --git a/client/cmd/add-entry/main.go b/client/cmd/add-entry/main.go deleted file mode 100644 index a29d01f..0000000 --- a/client/cmd/add-entry/main.go +++ /dev/null @@ -1,52 +0,0 @@ -package main - -import ( - "context" - "flag" - "fmt" - - "encoding/base64" - - "github.com/golang/glog" - "github.com/system-transparency/stfe/client" - "github.com/system-transparency/stfe/types" -) - -var ( - identifier = flag.String("identifier", "", "checksum identifier") - checksum = flag.String("checksum", "", "base64-encoded checksum") -) - -func main() { - flag.Parse() - defer glog.Flush() - - client, err := client.NewClientFromFlags() - if err != nil { - glog.Errorf("NewClientFromFlags: %v", err) - return - } - data, err := NewChecksumV1FromFlags() - if err != nil { - glog.Errorf("NewChecksumV1FromFlags: %v", err) - return - } - leafHash, err := client.AddEntry(context.Background(), data) - if err != nil { - glog.Errorf("AddEntry: %v", err) - return - } - fmt.Println("leaf hash:", base64.StdEncoding.EncodeToString(leafHash)) -} - -func NewChecksumV1FromFlags() (*types.ChecksumV1, error) { - var err error - data := types.ChecksumV1{ - Identifier: []byte(*identifier), - } - data.Checksum, err = base64.StdEncoding.DecodeString(*checksum) - if err != nil { - return nil, fmt.Errorf("entry_checksum: DecodeString: %v", err) - } - return &data, nil -} diff --git a/client/cmd/cosign/main.go b/client/cmd/cosign/main.go deleted file mode 100644 index e86842b..0000000 --- a/client/cmd/cosign/main.go +++ /dev/null @@ -1,56 +0,0 @@ -package main - -import ( - "bytes" - "crypto/ed25519" - "encoding/hex" - "flag" - "fmt" - "log" - "net/http" - - "github.com/system-transparency/stfe/types" -) - -var ( - url = flag.String("url", "http://localhost:6965/st/v0", "base url") - sk = flag.String("sk", "e1d7c494dacb0ddf809a17e4528b01f584af22e3766fa740ec52a1711c59500d711090dd2286040b50961b0fe09f58aa665ccee5cb7ee042d819f18f6ab5046b", "hex key") -) - -func main() { - priv, err := hex.DecodeString(*sk) - if err != nil { - log.Fatalf("DecodeString: %v", err) - } - sk := ed25519.PrivateKey(priv) - vk := sk.Public().(ed25519.PublicKey) - fmt.Printf("sk: %x\nvk: %x\n", sk, vk) - - rsp, err := http.Get(*url + "/get-tree-head-to-sign") - if err != nil { - log.Fatalf("Get: %v", err) - } - var sth types.SignedTreeHead - if err := sth.UnmarshalASCII(rsp.Body); err != nil { - log.Fatalf("UnmarshalASCII: %v", err) - } - fmt.Printf("%+v\n", sth) - - msg := sth.TreeHead.Marshal() - sig := ed25519.Sign(sk, msg) - sigident := &types.SigIdent{ - KeyHash: types.Hash(vk[:]), - Signature: &[types.SignatureSize]byte{}, - } - copy(sigident.Signature[:], sig) - - buf := bytes.NewBuffer(nil) - if err := sigident.MarshalASCII(buf); err != nil { - log.Fatalf("MarshalASCII: %v", err) - } - rsp, err = http.Post(*url+"/add-cosignature", "type/stfe", buf) - if err != nil { - log.Fatalf("Post: %v", err) - } - fmt.Printf("Status: %v\n", rsp.StatusCode) -} diff --git a/client/cmd/example.sh b/client/cmd/example.sh deleted file mode 100755 index d790712..0000000 --- a/client/cmd/example.sh +++ /dev/null @@ -1,49 +0,0 @@ -#!/bin/bash -set -eu - -log_url=http://tlog-poc.system-transparency.org:4780/st/v1 -log_id=AAG+ZW+UesWdMFytUGkp28csBcziomSB3U2vvkAW55MVZQ== -tmpdir=$(mktemp -dt stfe.XXXXXXXX) -cp $0 $tmpdir/ -cd $tmpdir - -commonargs="--log_id $log_id --log_url $log_url" # --logtostderr -v 3 -pause="sleep 1" - -echo "arguments used:" -echo $commonargs -echo "" - -echo "fetching sth..." -get-sth $commonargs | tee sth1.output -echo "" && $pause - -echo "adding an entry..." -add-entry $commonargs \ - --identifier "example.sh v0.0.1-$(cat /dev/urandom | base64 | head -c 10)" \ - --checksum $(sha256sum "$0") | tee add-entry.output -echo "" && $pause - -echo "fetching another sth..." -get-sth $commonargs | tee sth2.output -echo "" && $pause - -echo "verifying inclusion..." -get-proof-by-hash $commonargs \ - --leaf_hash $(cat add-entry.output | awk '{print $3}') \ - --sth $(cat sth2.output | awk '{print $2}') -echo "" && $pause - -echo "verifying consistency..." -get-consistency-proof $commonargs \ - --first $(cat sth1.output | awk '{print $2}') \ - --second $(cat sth2.output | awk '{print $2}') -echo "" && $pause - -echo "fetching the log's first entry..." -get-entries $commonargs --start 0 --end 0 -echo "" - -rm *.output $0 -cd -rmdir $tmpdir diff --git a/client/cmd/get-consistency-proof/main.go b/client/cmd/get-consistency-proof/main.go deleted file mode 100644 index bb8a7a6..0000000 --- a/client/cmd/get-consistency-proof/main.go +++ /dev/null @@ -1,70 +0,0 @@ -package main - -import ( - "context" - "flag" - "fmt" - - "encoding/base64" - - "github.com/golang/glog" - "github.com/system-transparency/stfe/client" - "github.com/system-transparency/stfe/types" -) - -var ( - first = flag.String("first", "", "base64-encoded sth") - second = flag.String("second", "", "base64-encoded sth") -) - -func main() { - flag.Parse() - defer glog.Flush() - - client, err := client.NewClientFromFlags() - if err != nil { - glog.Errorf("NewClientFromFlags: %v", err) - return - } - sth1, sth2, err := newParamsFromFlags() - if err != nil { - glog.Errorf("NewRequestFromFlags: %v", err) - return - } - - proof, err := client.GetConsistencyProof(context.Background(), sth1, sth2) - if err != nil { - glog.Errorf("GetConsistencyProof: %v", err) - return - } - serialized, err := types.Marshal(*proof) - if err != nil { - glog.Errorf("Marshal: %v", err) - return - } - fmt.Println("proof:", base64.StdEncoding.EncodeToString(serialized)) -} - -func newParamsFromFlags() (*types.StItem, *types.StItem, error) { - sth1, err := decodeSthStr(*first) - if err != nil { - return nil, nil, fmt.Errorf("first: decodeSthStr: %v", err) - } - sth2, err := decodeSthStr(*second) - if err != nil { - return nil, nil, fmt.Errorf("second: decodeSthStr: %v", err) - } - return sth1, sth2, nil -} - -func decodeSthStr(sthStr string) (*types.StItem, error) { - serialized, err := base64.StdEncoding.DecodeString(sthStr) - if err != nil { - return nil, fmt.Errorf("DecodeString: %v", err) - } - var item types.StItem - if err = types.Unmarshal(serialized, &item); err != nil { - return nil, fmt.Errorf("Unmarshal: %v", err) - } - return &item, nil -} diff --git a/client/cmd/get-entries/main.go b/client/cmd/get-entries/main.go deleted file mode 100644 index f32fdbf..0000000 --- a/client/cmd/get-entries/main.go +++ /dev/null @@ -1,83 +0,0 @@ -package main - -import ( - "context" - "flag" - "fmt" - - "encoding/base64" - - "github.com/golang/glog" - "github.com/google/trillian/merkle/rfc6962" - "github.com/system-transparency/stfe/client" - "github.com/system-transparency/stfe/types" -) - -var ( - start = flag.Uint64("start", 0, "inclusive start index to download") - end = flag.Uint64("end", 0, "inclusive stop index to download") -) - -func main() { - flag.Parse() - defer glog.Flush() - - client, err := client.NewClientFromFlags() - if err != nil { - glog.Errorf("NewClientFromFlags: %v", err) - return - } - items, err := getRange(client, *start, *end) - if err != nil { - glog.Errorf("getRange: %v", err) - return - } - if err := printRange(items); err != nil { - glog.Errorf("printRange: %v", err) - return - } -} - -func getRange(client *client.Client, start, end uint64) ([]*types.StItem, error) { - items := make([]*types.StItem, 0, end-start+1) - for len(items) != cap(items) { - rsp, err := client.GetEntries(context.Background(), start, end) - if err != nil { - return nil, fmt.Errorf("fetching entries failed: %v", err) - } - items = append(items, rsp...) - start += uint64(len(rsp)) - } - return items, nil -} - -func printRange(items []*types.StItem) error { - for i, item := range items { - var status string - msg, err := types.Marshal(item.SignedChecksumV1.Data) - if err != nil { - return fmt.Errorf("Marshal data failed: %v", err) - } - sig := item.SignedChecksumV1.Signature.Signature - namespace := &item.SignedChecksumV1.Signature.Namespace - if err := namespace.Verify(msg, sig); err != nil { - status = "unverified signature" - } else { - status = "verified signature" - } - serializedNamespace, err := types.Marshal(*namespace) - if err != nil { - return fmt.Errorf("Marshal namespace failed: %v", err) - } - serializedLeaf, err := types.Marshal(*item) - if err != nil { - return fmt.Errorf("Marshal item on index %d: %v", *start+uint64(i), err) - } - fmt.Printf("Index(%d) - %s\n", *start+uint64(i), status) - fmt.Printf("-> Namespace: %s\n", base64.StdEncoding.EncodeToString(serializedNamespace)) - fmt.Printf("-> Identifier: %s\n", string(item.SignedChecksumV1.Data.Identifier)) - fmt.Printf("-> Checksum: %s\n", base64.StdEncoding.EncodeToString(item.SignedChecksumV1.Data.Checksum)) - fmt.Printf("-> Leaf hash: %s\n", base64.StdEncoding.EncodeToString(rfc6962.DefaultHasher.HashLeaf(serializedLeaf))) - } - return nil -} diff --git a/client/cmd/get-proof-by-hash/main.go b/client/cmd/get-proof-by-hash/main.go deleted file mode 100644 index 1f4f304..0000000 --- a/client/cmd/get-proof-by-hash/main.go +++ /dev/null @@ -1,66 +0,0 @@ -package main - -import ( - "context" - "flag" - "fmt" - - "encoding/base64" - - "github.com/golang/glog" - "github.com/system-transparency/stfe/client" - "github.com/system-transparency/stfe/types" -) - -var ( - sthStr = flag.String("sth", "", "base64-encoded StItem of type StFormatSignedTreeHeadV1 (default: fetch new sth)") - leafHashStr = flag.String("leaf_hash", "", "base64-encoded leaf hash") -) - -func main() { - flag.Parse() - defer glog.Flush() - - client, err := client.NewClientFromFlags() - if err != nil { - glog.Errorf("NewClientFromFlags: %v", err) - return - } - leafHash, sth, err := newParamsFromFlags(client) - if err != nil { - glog.Errorf("NewRequestFromFlags: %v", err) - return - } - - proof, err := client.GetProofByHash(context.Background(), leafHash, sth) - if err != nil { - glog.Errorf("GetProofByHash: %v", err) - return - } - serialized, err := types.Marshal(*proof) - if err != nil { - glog.Errorf("Marshal: %v", err) - } - fmt.Println("proof:", base64.StdEncoding.EncodeToString(serialized)) -} - -func newParamsFromFlags(client *client.Client) ([]byte, *types.StItem, error) { - serialized, err := base64.StdEncoding.DecodeString(*sthStr) - if err != nil { - return nil, nil, fmt.Errorf("sth: DecodeString: %v", err) - } - var item types.StItem - if err = types.Unmarshal(serialized, &item); err != nil { - return nil, nil, fmt.Errorf("sth: Unmarshal: %v", err) - } else if got, want := item.Format, types.StFormatSignedTreeHeadV1; got != want { - return nil, nil, fmt.Errorf("unexpected StItem format: %v", got) - } - leafHash, err := base64.StdEncoding.DecodeString(*leafHashStr) - if err != nil { - return nil, nil, fmt.Errorf("leaf_hash: DecodeString: %v", err) - } else if got, want := len(leafHash), 32; got != want { - return nil, nil, fmt.Errorf("leaf_hash: unexpected size: %v", got) - } - glog.V(3).Infof("created request parameters TreeSize(%d) and LeafHash(%s)", item.SignedTreeHeadV1.TreeHead.TreeSize, *leafHashStr) - return leafHash, &item, nil -} diff --git a/client/cmd/get-sth/main.go b/client/cmd/get-sth/main.go deleted file mode 100644 index 6b23b06..0000000 --- a/client/cmd/get-sth/main.go +++ /dev/null @@ -1,35 +0,0 @@ -package main - -import ( - "context" - "flag" - "fmt" - - "encoding/base64" - - "github.com/golang/glog" - "github.com/system-transparency/stfe/client" - "github.com/system-transparency/stfe/types" -) - -func main() { - flag.Parse() - defer glog.Flush() - - client, err := client.NewClientFromFlags() - if err != nil { - glog.Errorf("NewClientFromFlags: %v", err) - return - } - sth, err := client.GetLatestSth(context.Background()) - if err != nil { - glog.Errorf("GetLatestSth: %v", err) - return - } - serialized, err := types.Marshal(*sth) - if err != nil { - glog.Errorf("Marshal: %v", err) - return - } - fmt.Println("sth:", base64.StdEncoding.EncodeToString(serialized)) -} diff --git a/client/cmd/keygen/main.go b/client/cmd/keygen/main.go deleted file mode 100644 index c1c1b58..0000000 --- a/client/cmd/keygen/main.go +++ /dev/null @@ -1,17 +0,0 @@ -package main - -import ( - "crypto/ed25519" - "crypto/rand" - "fmt" - "log" -) - -func main() { - vk, sk, err := ed25519.GenerateKey(rand.Reader) - if err != nil { - log.Fatalf("GenerateKey: %v", err) - } - fmt.Printf("sk: %x\n", sk[:]) - fmt.Printf("vk: %x\n", vk[:]) -} diff --git a/client/cmd/submit/main.go b/client/cmd/submit/main.go deleted file mode 100644 index 36c7271..0000000 --- a/client/cmd/submit/main.go +++ /dev/null @@ -1,28 +0,0 @@ -package main - -// go run . | bash - -import ( - "crypto/ed25519" - "crypto/rand" - "fmt" - "github.com/system-transparency/stfe/types" -) - -func main() { - checksum := [32]byte{} - msg := types.Message{ - ShardHint: 0, - Checksum: &checksum, - } - - vk, sk, err := ed25519.GenerateKey(rand.Reader) - if err != nil { - fmt.Printf("ed25519.GenerateKey: %v\n", err) - return - } - sig := ed25519.Sign(sk, msg.Marshal()) - //fmt.Printf("sk: %x\nvk: %x\n", sk[:], vk[:]) - - fmt.Printf("echo \"shard_hint=%d\nchecksum=%x\nsignature_over_message=%x\nverification_key=%x\ndomain_hint=%s\" | curl --data-binary @- localhost:6965/st/v0/add-leaf\n", msg.ShardHint, msg.Checksum[:], sig, vk[:], "example.com") -} diff --git a/client/flag.go b/client/flag.go deleted file mode 100644 index 8ba7a10..0000000 --- a/client/flag.go +++ /dev/null @@ -1,55 +0,0 @@ -package client - -import ( - "flag" - "fmt" - - "crypto/ed25519" - "encoding/base64" - "net/http" - - "github.com/system-transparency/stfe/types" -) - -var ( - logId = flag.String("log_id", "AAG+ZW+UesWdMFytUGkp28csBcziomSB3U2vvkAW55MVZQ==", "base64-encoded log identifier") - logUrl = flag.String("log_url", "http://tlog-poc.system-transparency.org:4780/st/v1", "log url") - ed25519_sk = flag.String("ed25519_sk", "d8i6nud7PS1vdO0sIk9H+W0nyxbM63Y3/mSeUPRafWaFh8iH8QXvL7NaAYn2RZPrnEey+FdpmTYXE47OFO70eg==", "base64-encoded ed25519 signing key") -) - -func NewClientFromFlags() (*Client, error) { - var err error - c := Client{ - HttpClient: &http.Client{}, - } - if len(*ed25519_sk) != 0 { - sk, err := base64.StdEncoding.DecodeString(*ed25519_sk) - if err != nil { - return nil, fmt.Errorf("ed25519_sk: DecodeString: %v", err) - } - c.Signer = ed25519.PrivateKey(sk) - c.Namespace, err = types.NewNamespaceEd25519V1([]byte(ed25519.PrivateKey(sk).Public().(ed25519.PublicKey))) - if err != nil { - return nil, fmt.Errorf("ed25519_vk: NewNamespaceEd25519V1: %v", err) - } - } - if c.Log, err = NewDescriptorFromFlags(); err != nil { - return nil, fmt.Errorf("NewDescriptorFromFlags: %v", err) - } - return &c, nil -} - -func NewDescriptorFromFlags() (*Descriptor, error) { - b, err := base64.StdEncoding.DecodeString(*logId) - if err != nil { - return nil, fmt.Errorf("LogId: DecodeString: %v", err) - } - var namespace types.Namespace - if err := types.Unmarshal(b, &namespace); err != nil { - return nil, fmt.Errorf("LogId: Unmarshal: %v", err) - } - return &Descriptor{ - Namespace: &namespace, - Url: *logUrl, - }, nil -} diff --git a/client/verify.go b/client/verify.go deleted file mode 100644 index c95828c..0000000 --- a/client/verify.go +++ /dev/null @@ -1,52 +0,0 @@ -package client - -import ( - "fmt" - "reflect" - - "github.com/google/trillian/merkle" - "github.com/google/trillian/merkle/rfc6962" - "github.com/system-transparency/stfe/types" -) - -func VerifySignedTreeHeadV1(namespace *types.Namespace, sth *types.StItem) error { - if got, want := &sth.SignedTreeHeadV1.Signature.Namespace, namespace; !reflect.DeepEqual(got, want) { - return fmt.Errorf("unexpected log id: %v", want) - } - th, err := types.Marshal(sth.SignedTreeHeadV1.TreeHead) - if err != nil { - return fmt.Errorf("Marshal: %v", err) - } - if err := namespace.Verify(th, sth.SignedTreeHeadV1.Signature.Signature); err != nil { - return fmt.Errorf("Verify: %v", err) - } - return nil -} - -func VerifyConsistencyProofV1(proof, first, second *types.StItem) error { - path := make([][]byte, 0, len(proof.ConsistencyProofV1.ConsistencyPath)) - for _, nh := range proof.ConsistencyProofV1.ConsistencyPath { - path = append(path, nh.Data) - } - return merkle.NewLogVerifier(rfc6962.DefaultHasher).VerifyConsistencyProof( - int64(proof.ConsistencyProofV1.TreeSize1), - int64(proof.ConsistencyProofV1.TreeSize2), - first.SignedTreeHeadV1.TreeHead.RootHash.Data, - second.SignedTreeHeadV1.TreeHead.RootHash.Data, - path, - ) -} - -func VerifyInclusionProofV1(proof, sth *types.StItem, leafHash []byte) error { - path := make([][]byte, 0, len(proof.InclusionProofV1.InclusionPath)) - for _, nh := range proof.InclusionProofV1.InclusionPath { - path = append(path, nh.Data) - } - return merkle.NewLogVerifier(rfc6962.DefaultHasher).VerifyInclusionProof( - int64(proof.InclusionProofV1.LeafIndex), - int64(proof.InclusionProofV1.TreeSize), - path, - sth.SignedTreeHeadV1.TreeHead.RootHash.Data, - leafHash, - ) -} diff --git a/cmd/siglog_server/.gitignore b/cmd/siglog_server/.gitignore new file mode 100644 index 0000000..254defd --- /dev/null +++ b/cmd/siglog_server/.gitignore @@ -0,0 +1 @@ +server diff --git a/cmd/siglog_server/README.md b/cmd/siglog_server/README.md new file mode 100644 index 0000000..71bb3ac --- /dev/null +++ b/cmd/siglog_server/README.md @@ -0,0 +1,60 @@ +# Run Trillian + STFE locally +Trillian uses a database. So, we will need to set that up. It is documented +[here](https://github.com/google/trillian#mysql-setup), and how to check that it +is setup properly +[here](https://github.com/google/certificate-transparency-go/blob/master/trillian/docs/ManualDeployment.md#data-storage). + +Other than the database we need the Trillian log signer, Trillian log server, +and STFE server. +``` +$ go install github.com/google/trillian/cmd/trillian_log_signer +$ go install github.com/google/trillian/cmd/trillian_log_server +$ go install +``` + +Start Trillian log signer: +``` +trillian_log_signer --logtostderr -v 9 --force_master --rpc_endpoint=localhost:6961 --http_endpoint=localhost:6964 --num_sequencers 1 --sequencer_interval 100ms --batch_size 100 +``` + +Start Trillian log server: +``` +trillian_log_server --logtostderr -v 9 --rpc_endpoint=localhost:6962 --http_endpoint=localhost:6963 +``` + +As described in more detail +[here](https://github.com/google/certificate-transparency-go/blob/master/trillian/docs/ManualDeployment.md#trillian-services), +we need to provision a Merkle tree once: +``` +$ go install github.com/google/trillian/cmd/createtree +$ createtree --admin_server localhost:6962 + +``` + +Hang on to ``. Our STFE server will use it when talking to the +Trillian log server to specify which Merkle tree we are working against. + +(If you take a look in the `Trees` table you will see that the tree has been +provisioned.) + +We will also need a public key-pair and log identifier for the STFE server. +``` +$ go install github.com/system-transparency/stfe/types/cmd/new-namespace +sk: +vk: +ed25519_v1: +``` + +The log's identifier is `` and contains the public verification key +``. The log's corresponding secret signing key is ``. + +Start STFE server: +``` +$ ./server --logtostderr -v 9 --http_endpoint localhost:6965 --log_rpc_server localhost:6962 --trillian_id --key +``` + +If the log is responsive on, e.g., `GET http://localhost:6965/st/v1/get-latest-sth` you +may want to try running +`github.com/system-transparency/stfe/client/cmd/example.sh`. You need to +configure the log's id though for verification to work (flag `log_id`, which +should be set to the `` output above). diff --git a/cmd/siglog_server/main.go b/cmd/siglog_server/main.go new file mode 100644 index 0000000..368b0a7 --- /dev/null +++ b/cmd/siglog_server/main.go @@ -0,0 +1,176 @@ +// Package main provides an STFE server binary +package main + +import ( + "context" + "crypto" + "crypto/ed25519" + "encoding/hex" + "flag" + "fmt" + "net/http" + "os" + "os/signal" + "strings" + "sync" + "syscall" + "time" + + "github.com/golang/glog" + "github.com/google/trillian" + "github.com/prometheus/client_golang/prometheus/promhttp" + stfe "github.com/system-transparency/stfe/pkg/instance" + "github.com/system-transparency/stfe/pkg/state" + trillianWrapper "github.com/system-transparency/stfe/pkg/trillian" + "github.com/system-transparency/stfe/pkg/types" + "google.golang.org/grpc" +) + +var ( + httpEndpoint = flag.String("http_endpoint", "localhost:6965", "host:port specification of where stfe serves clients") + rpcBackend = flag.String("log_rpc_server", "localhost:6962", "host:port specification of where Trillian serves clients") + prefix = flag.String("prefix", "", "a prefix that proceeds /st/v0/") + trillianID = flag.Int64("trillian_id", 0, "log identifier in the Trillian database") + deadline = flag.Duration("deadline", time.Second*10, "deadline for backend requests") + key = flag.String("key", "", "hex-encoded Ed25519 signing key") + witnesses = flag.String("witnesses", "", "comma-separated list of trusted witness verification keys in hex") + maxRange = flag.Int64("max_range", 10, "maximum number of entries that can be retrived in a single request") + interval = flag.Duration("interval", time.Second*30, "interval used to rotate the log's cosigned STH") +) + +func main() { + flag.Parse() + defer glog.Flush() + + // wait for clean-up before exit + var wg sync.WaitGroup + defer wg.Wait() + ctx, cancel := context.WithCancel(context.Background()) + defer cancel() + + glog.V(3).Infof("configuring stfe instance...") + instance, err := setupInstanceFromFlags() + if err != nil { + glog.Errorf("setupInstance: %v", err) + return + } + + glog.V(3).Infof("spawning state manager") + go func() { + wg.Add(1) + defer wg.Done() + instance.Stateman.Run(ctx) + glog.Errorf("state manager shutdown") + cancel() // must have state manager running + }() + + glog.V(3).Infof("spawning await") + server := http.Server{Addr: *httpEndpoint} + go await(ctx, func() { + wg.Add(1) + defer wg.Done() + ctxInner, _ := context.WithTimeout(ctx, time.Second*60) + glog.Infof("Shutting down HTTP server...") + server.Shutdown(ctxInner) + glog.V(3).Infof("HTTP server shutdown") + glog.Infof("Shutting down spawned go routines...") + cancel() + }) + + glog.Infof("Serving on %v/%v", *httpEndpoint, *prefix) + if err = server.ListenAndServe(); err != http.ErrServerClosed { + glog.Errorf("ListenAndServe: %v", err) + } +} + +// SetupInstance sets up a new STFE instance from flags +func setupInstanceFromFlags() (*stfe.Instance, error) { + var i stfe.Instance + var err error + + // Setup log configuration + i.Signer, i.LogID, err = newLogIdentity(*key) + if err != nil { + return nil, fmt.Errorf("newLogIdentity: %v", err) + } + i.TreeID = *trillianID + i.Prefix = *prefix + i.MaxRange = *maxRange + i.Deadline = *deadline + i.Interval = *interval + i.Witnesses, err = newWitnessMap(*witnesses) + if err != nil { + return nil, fmt.Errorf("newWitnessMap: %v", err) + } + + // Setup log client + dialOpts := []grpc.DialOption{grpc.WithInsecure(), grpc.WithBlock(), grpc.WithTimeout(i.Deadline)} + conn, err := grpc.Dial(*rpcBackend, dialOpts...) + if err != nil { + return nil, fmt.Errorf("Dial: %v", err) + } + i.Client = &trillianWrapper.TrillianClient{ + TreeID: i.TreeID, + GRPC: trillian.NewTrillianLogClient(conn), + } + + // Setup state manager + i.Stateman, err = state.NewStateManagerSingle(i.Client, i.Signer, i.Interval, i.Deadline) + if err != nil { + return nil, fmt.Errorf("NewStateManager: %v", err) + } + + // Register HTTP endpoints + mux := http.NewServeMux() + http.Handle("/", mux) + for _, handler := range i.Handlers() { + glog.V(3).Infof("adding handler: %s", handler.Path()) + mux.Handle(handler.Path(), handler) + } + glog.V(3).Infof("Adding prometheus handler on path: /metrics") + http.Handle("/metrics", promhttp.Handler()) + + return &i, nil +} + +func newLogIdentity(key string) (crypto.Signer, string, error) { + buf, err := hex.DecodeString(key) + if err != nil { + return nil, "", fmt.Errorf("DecodeString: %v", err) + } + sk := crypto.Signer(ed25519.PrivateKey(buf)) + vk := sk.Public().(ed25519.PublicKey) + return sk, hex.EncodeToString([]byte(vk[:])), nil +} + +// newWitnessMap creates a new map of trusted witnesses +func newWitnessMap(witnesses string) (map[[types.HashSize]byte][types.VerificationKeySize]byte, error) { + w := make(map[[types.HashSize]byte][types.VerificationKeySize]byte) + if len(witnesses) > 0 { + for _, witness := range strings.Split(witnesses, ",") { + b, err := hex.DecodeString(witness) + if err != nil { + return nil, fmt.Errorf("DecodeString: %v", err) + } + + var vk [types.VerificationKeySize]byte + if n := copy(vk[:], b); n != types.VerificationKeySize { + return nil, fmt.Errorf("Invalid verification key size: %v", n) + } + w[*types.Hash(vk[:])] = vk + } + } + return w, nil +} + +// await waits for a shutdown signal and then runs a clean-up function +func await(ctx context.Context, done func()) { + sigs := make(chan os.Signal, 1) + signal.Notify(sigs, syscall.SIGINT, syscall.SIGTERM) + select { + case <-sigs: + case <-ctx.Done(): + } + glog.V(3).Info("received shutdown signal") + done() +} diff --git a/cmd/tmp/README.md b/cmd/tmp/README.md new file mode 100644 index 0000000..30d5317 --- /dev/null +++ b/cmd/tmp/README.md @@ -0,0 +1,2 @@ +# Warning +These basic commands will be moved or replaced by proper tooling. diff --git a/cmd/tmp/cosign/main.go b/cmd/tmp/cosign/main.go new file mode 100644 index 0000000..a51f17d --- /dev/null +++ b/cmd/tmp/cosign/main.go @@ -0,0 +1,56 @@ +package main + +import ( + "bytes" + "crypto/ed25519" + "encoding/hex" + "flag" + "fmt" + "log" + "net/http" + + "github.com/system-transparency/stfe/pkg/types" +) + +var ( + url = flag.String("url", "http://localhost:6965/st/v0", "base url") + sk = flag.String("sk", "e1d7c494dacb0ddf809a17e4528b01f584af22e3766fa740ec52a1711c59500d711090dd2286040b50961b0fe09f58aa665ccee5cb7ee042d819f18f6ab5046b", "hex key") +) + +func main() { + priv, err := hex.DecodeString(*sk) + if err != nil { + log.Fatalf("DecodeString: %v", err) + } + sk := ed25519.PrivateKey(priv) + vk := sk.Public().(ed25519.PublicKey) + fmt.Printf("sk: %x\nvk: %x\n", sk, vk) + + rsp, err := http.Get(*url + "/get-tree-head-to-sign") + if err != nil { + log.Fatalf("Get: %v", err) + } + var sth types.SignedTreeHead + if err := sth.UnmarshalASCII(rsp.Body); err != nil { + log.Fatalf("UnmarshalASCII: %v", err) + } + fmt.Printf("%+v\n", sth) + + msg := sth.TreeHead.Marshal() + sig := ed25519.Sign(sk, msg) + sigident := &types.SigIdent{ + KeyHash: types.Hash(vk[:]), + Signature: &[types.SignatureSize]byte{}, + } + copy(sigident.Signature[:], sig) + + buf := bytes.NewBuffer(nil) + if err := sigident.MarshalASCII(buf); err != nil { + log.Fatalf("MarshalASCII: %v", err) + } + rsp, err = http.Post(*url+"/add-cosignature", "type/stfe", buf) + if err != nil { + log.Fatalf("Post: %v", err) + } + fmt.Printf("Status: %v\n", rsp.StatusCode) +} diff --git a/cmd/tmp/keygen/main.go b/cmd/tmp/keygen/main.go new file mode 100644 index 0000000..c1c1b58 --- /dev/null +++ b/cmd/tmp/keygen/main.go @@ -0,0 +1,17 @@ +package main + +import ( + "crypto/ed25519" + "crypto/rand" + "fmt" + "log" +) + +func main() { + vk, sk, err := ed25519.GenerateKey(rand.Reader) + if err != nil { + log.Fatalf("GenerateKey: %v", err) + } + fmt.Printf("sk: %x\n", sk[:]) + fmt.Printf("vk: %x\n", vk[:]) +} diff --git a/cmd/tmp/submit/main.go b/cmd/tmp/submit/main.go new file mode 100644 index 0000000..3dcaa97 --- /dev/null +++ b/cmd/tmp/submit/main.go @@ -0,0 +1,29 @@ +package main + +// go run . | bash + +import ( + "crypto/ed25519" + "crypto/rand" + "fmt" + + "github.com/system-transparency/stfe/pkg/types" +) + +func main() { + checksum := [32]byte{} + msg := types.Message{ + ShardHint: 0, + Checksum: &checksum, + } + + vk, sk, err := ed25519.GenerateKey(rand.Reader) + if err != nil { + fmt.Printf("ed25519.GenerateKey: %v\n", err) + return + } + sig := ed25519.Sign(sk, msg.Marshal()) + //fmt.Printf("sk: %x\nvk: %x\n", sk[:], vk[:]) + + fmt.Printf("echo \"shard_hint=%d\nchecksum=%x\nsignature_over_message=%x\nverification_key=%x\ndomain_hint=%s\" | curl --data-binary @- localhost:6965/st/v0/add-leaf\n", msg.ShardHint, msg.Checksum[:], sig, vk[:], "example.com") +} diff --git a/doc.go b/doc.go deleted file mode 100644 index 4e86552..0000000 --- a/doc.go +++ /dev/null @@ -1,3 +0,0 @@ -// Package stfe implements a System Transparency Front-End (STFE) personality -// for the Trillian log server gRPC API. -package stfe diff --git a/endpoint.go b/endpoint.go deleted file mode 100644 index 9be55b4..0000000 --- a/endpoint.go +++ /dev/null @@ -1,163 +0,0 @@ -package stfe - -import ( - "context" - "crypto/ed25519" - "fmt" - "net/http" - - "github.com/golang/glog" - "github.com/google/trillian" - "github.com/system-transparency/stfe/types" -) - -func addEntry(ctx context.Context, i *Instance, w http.ResponseWriter, r *http.Request) (int, error) { - glog.V(3).Info("handling add-entry request") - leaf, err := i.LogParameters.parseAddEntryV1Request(r) - if err != nil { - return http.StatusBadRequest, fmt.Errorf("parseAddEntryV1Request: %v", err) - } - trsp, err := i.Client.QueueLeaf(ctx, &trillian.QueueLeafRequest{ - LogId: i.LogParameters.TreeId, - Leaf: &trillian.LogLeaf{ - LeafValue: leaf.Marshal(), - ExtraData: nil, - }, - }) - if errInner := checkQueueLeaf(trsp, err); errInner != nil { - return http.StatusInternalServerError, fmt.Errorf("bad QueueLeafResponse: %v", errInner) - } - return http.StatusOK, nil -} - -func addCosignature(ctx context.Context, i *Instance, w http.ResponseWriter, r *http.Request) (int, error) { - glog.V(3).Info("handling add-cosignature request") - req, err := i.LogParameters.parseAddCosignatureRequest(r) - if err != nil { - return http.StatusBadRequest, fmt.Errorf("parseAddCosignatureRequest: %v", err) - } - vk := i.LogParameters.Witnesses[*req.KeyHash] - if err := i.SthSource.AddCosignature(ctx, ed25519.PublicKey(vk[:]), req.Signature); err != nil { - return http.StatusBadRequest, fmt.Errorf("AddCosignature: %v", err) - } - return http.StatusOK, nil -} - -func getLatestSth(ctx context.Context, i *Instance, w http.ResponseWriter, _ *http.Request) (int, error) { - glog.V(3).Info("handling get-latest-sth request") - sth, err := i.SthSource.Latest(ctx) - if err != nil { - return http.StatusInternalServerError, fmt.Errorf("Latest: %v", err) - } - if err := sth.MarshalASCII(w); err != nil { - return http.StatusInternalServerError, fmt.Errorf("MarshalASCII: %v", err) - } - return http.StatusOK, nil -} - -func getStableSth(ctx context.Context, i *Instance, w http.ResponseWriter, _ *http.Request) (int, error) { - glog.V(3).Info("handling get-stable-sth request") - sth, err := i.SthSource.Stable(ctx) - if err != nil { - return http.StatusInternalServerError, fmt.Errorf("Latest: %v", err) - } - if err := sth.MarshalASCII(w); err != nil { - return http.StatusInternalServerError, fmt.Errorf("MarshalASCII: %v", err) - } - return http.StatusOK, nil -} - -func getCosignedSth(ctx context.Context, i *Instance, w http.ResponseWriter, _ *http.Request) (int, error) { - glog.V(3).Info("handling get-cosigned-sth request") - sth, err := i.SthSource.Cosigned(ctx) - if err != nil { - return http.StatusInternalServerError, fmt.Errorf("Cosigned: %v", err) - } - if err := sth.MarshalASCII(w); err != nil { - return http.StatusInternalServerError, fmt.Errorf("MarshalASCII: %v", err) - } - return http.StatusOK, nil -} - -func getConsistencyProof(ctx context.Context, i *Instance, w http.ResponseWriter, r *http.Request) (int, error) { - glog.V(3).Info("handling get-consistency-proof request") - req, err := i.LogParameters.parseGetConsistencyProofRequest(r) - if err != nil { - return http.StatusBadRequest, err - } - - trsp, err := i.Client.GetConsistencyProof(ctx, &trillian.GetConsistencyProofRequest{ - LogId: i.LogParameters.TreeId, - FirstTreeSize: int64(req.OldSize), - SecondTreeSize: int64(req.NewSize), - }) - if errInner := checkGetConsistencyProof(i.LogParameters, trsp, err); errInner != nil { - return http.StatusInternalServerError, fmt.Errorf("bad GetConsistencyProofResponse: %v", errInner) - } - - proof := &types.ConsistencyProof{ - NewSize: req.NewSize, - OldSize: req.OldSize, - Path: NodePathFromHashes(trsp.Proof.Hashes), - } - if err := proof.MarshalASCII(w); err != nil { - return http.StatusInternalServerError, fmt.Errorf("MarshalASCII: %v", err) - } - return http.StatusOK, nil -} - -func getProofByHash(ctx context.Context, i *Instance, w http.ResponseWriter, r *http.Request) (int, error) { - glog.V(3).Info("handling get-proof-by-hash request") - req, err := i.LogParameters.parseGetProofByHashRequest(r) - if err != nil { - return http.StatusBadRequest, err - } - - trsp, err := i.Client.GetInclusionProofByHash(ctx, &trillian.GetInclusionProofByHashRequest{ - LogId: i.LogParameters.TreeId, - LeafHash: req.LeafHash[:], - TreeSize: int64(req.TreeSize), - OrderBySequence: true, - }) - if errInner := checkGetInclusionProofByHash(i.LogParameters, trsp, err); errInner != nil { - return http.StatusInternalServerError, fmt.Errorf("bad GetInclusionProofByHashResponse: %v", errInner) - } - - proof := &types.InclusionProof{ - TreeSize: req.TreeSize, - LeafIndex: uint64(trsp.Proof[0].LeafIndex), - Path: NodePathFromHashes(trsp.Proof[0].Hashes), - } - if err := proof.MarshalASCII(w); err != nil { - return http.StatusInternalServerError, fmt.Errorf("MarshalASCII: %v", err) - } - return http.StatusOK, nil -} - -func getEntries(ctx context.Context, i *Instance, w http.ResponseWriter, r *http.Request) (int, error) { - glog.V(3).Info("handling get-entries request") - req, err := i.LogParameters.parseGetEntriesRequest(r) - if err != nil { - return http.StatusBadRequest, err - } - - trsp, err := i.Client.GetLeavesByRange(ctx, &trillian.GetLeavesByRangeRequest{ - LogId: i.LogParameters.TreeId, - StartIndex: int64(req.StartSize), - Count: int64(req.EndSize-req.StartSize) + 1, - }) - if errInner := checkGetLeavesByRange(req, trsp, err); errInner != nil { - return http.StatusInternalServerError, fmt.Errorf("checkGetLeavesByRangeResponse: %v", errInner) // there is one StatusBadRequest in here tho.. - } - - for _, serialized := range trsp.Leaves { - var leaf types.Leaf - if err := leaf.Unmarshal(serialized.LeafValue); err != nil { - return http.StatusInternalServerError, fmt.Errorf("Unmarshal: %v", err) - } - if err := leaf.MarshalASCII(w); err != nil { - return http.StatusInternalServerError, fmt.Errorf("MarshalASCII: %v", err) - } - } - return http.StatusOK, nil -} diff --git a/endpoint_test.go b/endpoint_test.go deleted file mode 100644 index e515635..0000000 --- a/endpoint_test.go +++ /dev/null @@ -1,481 +0,0 @@ -package stfe - -import ( - "bytes" - "context" - "fmt" - "reflect" - "testing" - - "net/http" - "net/http/httptest" - - "github.com/golang/mock/gomock" - cttestdata "github.com/google/certificate-transparency-go/trillian/testdata" - "github.com/google/trillian" - "github.com/system-transparency/stfe/testdata" - "github.com/system-transparency/stfe/types" -) - -func TestEndpointAddEntry(t *testing.T) { - for _, table := range []struct { - description string - breq *bytes.Buffer - trsp *trillian.QueueLeafResponse - terr error - wantCode int - }{ - { - description: "invalid: bad request: empty", - breq: bytes.NewBuffer(nil), - wantCode: http.StatusBadRequest, - }, - { - description: "invalid: bad Trillian response: error", - breq: testdata.AddSignedChecksumBuffer(t, testdata.Ed25519SkSubmitter, testdata.Ed25519VkSubmitter), - terr: fmt.Errorf("backend failure"), - wantCode: http.StatusInternalServerError, - }, - { - description: "valid", - breq: testdata.AddSignedChecksumBuffer(t, testdata.Ed25519SkSubmitter, testdata.Ed25519VkSubmitter), - trsp: testdata.DefaultTQlr(t, false), - wantCode: http.StatusOK, - }, - } { - func() { // run deferred functions at the end of each iteration - ti := newTestInstance(t, nil) - defer ti.ctrl.Finish() - - url := EndpointAddEntry.Path("http://example.com", ti.instance.LogParameters.Prefix) - req, err := http.NewRequest("POST", url, table.breq) - if err != nil { - t.Fatalf("must create http request: %v", err) - } - req.Header.Set("Content-Type", "application/octet-stream") - if table.trsp != nil || table.terr != nil { - ti.client.EXPECT().QueueLeaf(newDeadlineMatcher(), gomock.Any()).Return(table.trsp, table.terr) - } - - w := httptest.NewRecorder() - ti.postHandler(t, EndpointAddEntry).ServeHTTP(w, req) - if got, want := w.Code, table.wantCode; got != want { - t.Errorf("got error code %d but wanted %d in test %q", got, want, table.description) - } - }() - } -} - -func TestEndpointAddCosignature(t *testing.T) { - for _, table := range []struct { - description string - breq *bytes.Buffer - wantCode int - }{ - { - description: "invalid: bad request: empty", - breq: bytes.NewBuffer(nil), - wantCode: http.StatusBadRequest, - }, - { - description: "invalid: signed wrong sth", // newLogParameters() use testdata.Ed25519VkLog as default - breq: testdata.AddCosignatureBuffer(t, testdata.DefaultSth(t, testdata.Ed25519VkLog2), &testdata.Ed25519SkWitness, &testdata.Ed25519VkWitness), - wantCode: http.StatusBadRequest, - }, - { - description: "valid", - breq: testdata.AddCosignatureBuffer(t, testdata.DefaultSth(t, testdata.Ed25519VkLog), &testdata.Ed25519SkWitness, &testdata.Ed25519VkWitness), - wantCode: http.StatusOK, - }, - } { - func() { // run deferred functions at the end of each iteration - ti := newTestInstance(t, nil) - defer ti.ctrl.Finish() - - url := EndpointAddCosignature.Path("http://example.com", ti.instance.LogParameters.Prefix) - req, err := http.NewRequest("POST", url, table.breq) - if err != nil { - t.Fatalf("must create http request: %v", err) - } - req.Header.Set("Content-Type", "application/octet-stream") - - w := httptest.NewRecorder() - ti.postHandler(t, EndpointAddCosignature).ServeHTTP(w, req) - if got, want := w.Code, table.wantCode; got != want { - t.Errorf("got error code %d but wanted %d in test %q", got, want, table.description) - } - }() - } -} - -func TestEndpointGetLatestSth(t *testing.T) { - for _, table := range []struct { - description string - trsp *trillian.GetLatestSignedLogRootResponse - terr error - wantCode int - wantItem *types.StItem - }{ - { - description: "backend failure", - terr: fmt.Errorf("backend failure"), - wantCode: http.StatusInternalServerError, - }, - { - description: "valid", - trsp: testdata.DefaultTSlr(t), - wantCode: http.StatusOK, - wantItem: testdata.DefaultSth(t, testdata.Ed25519VkLog), - }, - } { - func() { // run deferred functions at the end of each iteration - ti := newTestInstance(t, cttestdata.NewSignerWithFixedSig(nil, testdata.Signature)) - ti.ctrl.Finish() - - // Setup and run client query - url := EndpointGetLatestSth.Path("http://example.com", ti.instance.LogParameters.Prefix) - req, err := http.NewRequest("GET", url, nil) - if err != nil { - t.Fatalf("must create http request: %v", err) - } - if table.trsp != nil || table.terr != nil { - ti.client.EXPECT().GetLatestSignedLogRoot(newDeadlineMatcher(), gomock.Any()).Return(table.trsp, table.terr) - } - - w := httptest.NewRecorder() - ti.getHandler(t, EndpointGetLatestSth).ServeHTTP(w, req) - if got, want := w.Code, table.wantCode; got != want { - t.Errorf("got error code %d but wanted %d in test %q", got, want, table.description) - } - if w.Code != http.StatusOK { - return - } - - var item types.StItem - if err := types.Unmarshal([]byte(w.Body.String()), &item); err != nil { - t.Errorf("valid response cannot be unmarshalled in test %q: %v", table.description, err) - } - if got, want := item, *table.wantItem; !reflect.DeepEqual(got, want) { - t.Errorf("got item\n%v\n\tbut wanted\n%v\n\tin test %q", got, want, table.description) - } - }() - } -} - -func TestEndpointGetStableSth(t *testing.T) { - for _, table := range []struct { - description string - useBadSource bool - wantCode int - wantItem *types.StItem - }{ - { - description: "invalid: sth source failure", - useBadSource: true, - wantCode: http.StatusInternalServerError, - }, - { - description: "valid", - wantCode: http.StatusOK, - wantItem: testdata.DefaultSth(t, testdata.Ed25519VkLog), - }, - } { - func() { // run deferred functions at the end of each iteration - ti := newTestInstance(t, nil) - ti.ctrl.Finish() - if table.useBadSource { - ti.instance.SthSource = &ActiveSthSource{} - } - - // Setup and run client query - url := EndpointGetStableSth.Path("http://example.com", ti.instance.LogParameters.Prefix) - req, err := http.NewRequest("GET", url, nil) - if err != nil { - t.Fatalf("must create http request: %v", err) - } - - w := httptest.NewRecorder() - ti.getHandler(t, EndpointGetStableSth).ServeHTTP(w, req) - if got, want := w.Code, table.wantCode; got != want { - t.Errorf("got error code %d but wanted %d in test %q", got, want, table.description) - } - if w.Code != http.StatusOK { - return - } - - var item types.StItem - if err := types.Unmarshal([]byte(w.Body.String()), &item); err != nil { - t.Errorf("valid response cannot be unmarshalled in test %q: %v", table.description, err) - } - if got, want := item, *table.wantItem; !reflect.DeepEqual(got, want) { - t.Errorf("got item\n%v\n\tbut wanted\n%v\n\tin test %q", got, want, table.description) - } - }() - } -} - -func TestEndpointGetCosignedSth(t *testing.T) { - for _, table := range []struct { - description string - useBadSource bool - wantCode int - wantItem *types.StItem - }{ - { - description: "invalid: sth source failure", - useBadSource: true, - wantCode: http.StatusInternalServerError, - }, - { - description: "valid", - wantCode: http.StatusOK, - wantItem: testdata.DefaultCosth(t, testdata.Ed25519VkLog, [][32]byte{testdata.Ed25519VkWitness}), - }, - } { - func() { // run deferred functions at the end of each iteration - ti := newTestInstance(t, nil) - ti.ctrl.Finish() - if table.useBadSource { - ti.instance.SthSource = &ActiveSthSource{} - } - - // Setup and run client query - url := EndpointGetCosignedSth.Path("http://example.com", ti.instance.LogParameters.Prefix) - req, err := http.NewRequest("GET", url, nil) - if err != nil { - t.Fatalf("must create http request: %v", err) - } - - w := httptest.NewRecorder() - ti.getHandler(t, EndpointGetCosignedSth).ServeHTTP(w, req) - if got, want := w.Code, table.wantCode; got != want { - t.Errorf("got error code %d but wanted %d in test %q", got, want, table.description) - } - if w.Code != http.StatusOK { - return - } - - var item types.StItem - if err := types.Unmarshal([]byte(w.Body.String()), &item); err != nil { - t.Errorf("valid response cannot be unmarshalled in test %q: %v", table.description, err) - } - if got, want := item, *table.wantItem; !reflect.DeepEqual(got, want) { - t.Errorf("got item\n%v\n\tbut wanted\n%v\n\tin test %q", got, want, table.description) - } - }() - } -} - -func TestEndpointGetProofByHash(t *testing.T) { - for _, table := range []struct { - description string - breq *bytes.Buffer - trsp *trillian.GetInclusionProofByHashResponse - terr error - wantCode int - wantItem *types.StItem - }{ - { - description: "invalid: bad request: empty", - breq: bytes.NewBuffer(nil), - wantCode: http.StatusBadRequest, - }, - { - description: "invalid: bad Trillian response: error", - breq: bytes.NewBuffer(marshal(t, types.GetProofByHashV1{TreeSize: 1, Hash: testdata.LeafHash})), - terr: fmt.Errorf("backend failure"), - wantCode: http.StatusInternalServerError, - }, - { - description: "valid", - breq: bytes.NewBuffer(marshal(t, types.GetProofByHashV1{TreeSize: 1, Hash: testdata.LeafHash})), - trsp: testdata.DefaultTGipbhr(t), - wantCode: http.StatusOK, - wantItem: testdata.DefaultInclusionProof(t, 1), - }, - } { - func() { // run deferred functions at the end of each iteration - ti := newTestInstance(t, nil) - defer ti.ctrl.Finish() - - url := EndpointGetProofByHash.Path("http://example.com", ti.instance.LogParameters.Prefix) - req, err := http.NewRequest("POST", url, table.breq) - if err != nil { - t.Fatalf("must create http request: %v", err) - } - req.Header.Set("Content-Type", "application/octet-stream") - if table.trsp != nil || table.terr != nil { - ti.client.EXPECT().GetInclusionProofByHash(newDeadlineMatcher(), gomock.Any()).Return(table.trsp, table.terr) - } - - w := httptest.NewRecorder() - ti.postHandler(t, EndpointGetProofByHash).ServeHTTP(w, req) - if got, want := w.Code, table.wantCode; got != want { - t.Errorf("got error code %d but wanted %d in test %q", got, want, table.description) - } - if w.Code != http.StatusOK { - return - } - - var item types.StItem - if err := types.Unmarshal([]byte(w.Body.String()), &item); err != nil { - t.Errorf("valid response cannot be unmarshalled in test %q: %v", table.description, err) - } - if got, want := item, *table.wantItem; !reflect.DeepEqual(got, want) { - t.Errorf("got item\n%v\n\tbut wanted\n%v\n\tin test %q", got, want, table.description) - } - }() - } -} - -func TestEndpointGetConsistencyProof(t *testing.T) { - for _, table := range []struct { - description string - breq *bytes.Buffer - trsp *trillian.GetConsistencyProofResponse - terr error - wantCode int - wantItem *types.StItem - }{ - { - description: "invalid: bad request: empty", - breq: bytes.NewBuffer(nil), - wantCode: http.StatusBadRequest, - }, - { - description: "invalid: bad Trillian response: error", - breq: bytes.NewBuffer(marshal(t, types.GetConsistencyProofV1{First: 1, Second: 2})), - terr: fmt.Errorf("backend failure"), - wantCode: http.StatusInternalServerError, - }, - { - description: "valid", - breq: bytes.NewBuffer(marshal(t, types.GetConsistencyProofV1{First: 1, Second: 2})), - trsp: testdata.DefaultTGcpr(t), - wantCode: http.StatusOK, - wantItem: testdata.DefaultConsistencyProof(t, 1, 2), - }, - } { - func() { // run deferred functions at the end of each iteration - ti := newTestInstance(t, nil) - defer ti.ctrl.Finish() - - url := EndpointGetConsistencyProof.Path("http://example.com", ti.instance.LogParameters.Prefix) - req, err := http.NewRequest("POST", url, table.breq) - if err != nil { - t.Fatalf("must create http request: %v", err) - } - req.Header.Set("Content-Type", "application/octet-stream") - if table.trsp != nil || table.terr != nil { - ti.client.EXPECT().GetConsistencyProof(newDeadlineMatcher(), gomock.Any()).Return(table.trsp, table.terr) - } - - w := httptest.NewRecorder() - ti.postHandler(t, EndpointGetConsistencyProof).ServeHTTP(w, req) - if got, want := w.Code, table.wantCode; got != want { - t.Errorf("got error code %d but wanted %d in test %q", got, want, table.description) - } - if w.Code != http.StatusOK { - return - } - - var item types.StItem - if err := types.Unmarshal([]byte(w.Body.String()), &item); err != nil { - t.Errorf("valid response cannot be unmarshalled in test %q: %v", table.description, err) - } - if got, want := item, *table.wantItem; !reflect.DeepEqual(got, want) { - t.Errorf("got item\n%v\n\tbut wanted\n%v\n\tin test %q", got, want, table.description) - } - }() - } -} - -func TestEndpointGetEntriesV1(t *testing.T) { - for _, table := range []struct { - description string - breq *bytes.Buffer - trsp *trillian.GetLeavesByRangeResponse - terr error - wantCode int - wantItem *types.StItemList - }{ - { - description: "invalid: bad request: empty", - breq: bytes.NewBuffer(nil), - wantCode: http.StatusBadRequest, - }, - { - description: "invalid: bad Trillian response: error", - breq: bytes.NewBuffer(marshal(t, types.GetEntriesV1{Start: 0, End: 0})), - terr: fmt.Errorf("backend failure"), - wantCode: http.StatusInternalServerError, - }, - { - description: "valid", // remember that newLogParameters() have testdata.MaxRange configured - breq: bytes.NewBuffer(marshal(t, types.GetEntriesV1{Start: 0, End: uint64(testdata.MaxRange - 1)})), - trsp: testdata.DefaultTGlbrr(t, 0, testdata.MaxRange-1), - wantCode: http.StatusOK, - wantItem: testdata.DefaultStItemList(t, 0, uint64(testdata.MaxRange)-1), - }, - } { - func() { // run deferred functions at the end of each iteration - ti := newTestInstance(t, nil) - defer ti.ctrl.Finish() - - url := EndpointGetEntries.Path("http://example.com", ti.instance.LogParameters.Prefix) - req, err := http.NewRequest("POST", url, table.breq) - if err != nil { - t.Fatalf("must create http request: %v", err) - } - req.Header.Set("Content-Type", "application/octet-stream") - if table.trsp != nil || table.terr != nil { - ti.client.EXPECT().GetLeavesByRange(newDeadlineMatcher(), gomock.Any()).Return(table.trsp, table.terr) - } - - w := httptest.NewRecorder() - ti.postHandler(t, EndpointGetEntries).ServeHTTP(w, req) - if got, want := w.Code, table.wantCode; got != want { - t.Errorf("got error code %d but wanted %d in test %q", got, want, table.description) - } - if w.Code != http.StatusOK { - return - } - - var item types.StItemList - if err := types.Unmarshal([]byte(w.Body.String()), &item); err != nil { - t.Errorf("valid response cannot be unmarshalled in test %q: %v", table.description, err) - } - if got, want := item, *table.wantItem; !reflect.DeepEqual(got, want) { - t.Errorf("got item\n%v\n\tbut wanted\n%v\n\tin test %q", got, want, table.description) - } - }() - } -} - -// TODO: TestWriteOctetResponse -func TestWriteOctetResponse(t *testing.T) { -} - -// deadlineMatcher implements gomock.Matcher, such that an error is raised if -// there is no context.Context deadline set -type deadlineMatcher struct{} - -// newDeadlineMatcher returns a new DeadlineMatcher -func newDeadlineMatcher() gomock.Matcher { - return &deadlineMatcher{} -} - -// Matches returns true if the passed interface is a context with a deadline -func (dm *deadlineMatcher) Matches(i interface{}) bool { - ctx, ok := i.(context.Context) - if !ok { - return false - } - _, ok = ctx.Deadline() - return ok -} - -// String is needed to implement gomock.Matcher -func (dm *deadlineMatcher) String() string { - return fmt.Sprintf("deadlineMatcher{}") -} diff --git a/go.sum b/go.sum index 68617e4..839df30 100644 --- a/go.sum +++ b/go.sum @@ -181,6 +181,7 @@ github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMyw github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.2/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.5.4 h1:L8R9j+yAqZuZjsqh/z+F1NCffTKKLShY6zXTItVIZ8M= github.com/google/go-cmp v0.5.4/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= github.com/google/martian v2.1.0+incompatible/go.mod h1:9I4somxYTbIHy5NJKHRl3wXiIaQGbYVAs8BPL6v8lEs= @@ -461,6 +462,7 @@ golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8U golang.org/x/crypto v0.0.0-20190605123033-f99c8df09eb5/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20190701094942-4def268fd1a4/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= +golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9 h1:psW17arqaxU48Z5kZ0CQnkZWQJsqcURM6tKiBApRjXI= golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= @@ -645,6 +647,7 @@ golang.org/x/tools v0.0.0-20200630154851-b2d8b0336632/go.mod h1:EkVYQZoAsY45+roY golang.org/x/tools v0.0.0-20200706234117-b22de6825cf7/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA= golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543 h1:E7g+9GITq07hpfrRu66IVDexMakfv52eLZ2CXBWiKr4= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= google.golang.org/api v0.3.1/go.mod h1:6wY9I6uQWHQ8EM57III9mq/AjF+i8G65rmVagqKMtkk= google.golang.org/api v0.4.0/go.mod h1:8k5glujaEP+g9n7WNsDg8QP6cUVNI86fCNMcbazEtwE= diff --git a/instance.go b/instance.go deleted file mode 100644 index 4425770..0000000 --- a/instance.go +++ /dev/null @@ -1,75 +0,0 @@ -package stfe - -import ( - "context" - "fmt" - "time" - - "net/http" - - "github.com/golang/glog" - "github.com/google/trillian" - "github.com/system-transparency/stfe/types" -) - -// Instance is an instance of the system transparency front-end -type Instance struct { - Client trillian.TrillianLogClient - LogParameters *LogParameters - SthSource SthSource -} - -// Handlers returns a list of STFE handlers -func (i *Instance) Handlers() []Handler { - return []Handler{ - Handler{Instance: i, Handler: addEntry, Endpoint: types.EndpointAddLeaf, Method: http.MethodPost}, - Handler{Instance: i, Handler: addCosignature, Endpoint: types.EndpointAddCosignature, Method: http.MethodPost}, - Handler{Instance: i, Handler: getLatestSth, Endpoint: types.EndpointGetTreeHeadLatest, Method: http.MethodGet}, - Handler{Instance: i, Handler: getStableSth, Endpoint: types.EndpointGetTreeHeadToSign, Method: http.MethodGet}, - Handler{Instance: i, Handler: getCosignedSth, Endpoint: types.EndpointGetTreeHeadCosigned, Method: http.MethodGet}, - Handler{Instance: i, Handler: getProofByHash, Endpoint: types.EndpointGetProofByHash, Method: http.MethodPost}, - Handler{Instance: i, Handler: getConsistencyProof, Endpoint: types.EndpointGetConsistencyProof, Method: http.MethodPost}, - Handler{Instance: i, Handler: getEntries, Endpoint: types.EndpointGetLeaves, Method: http.MethodPost}, - } -} - -// Handler implements the http.Handler interface, and contains a reference -// to an STFE server instance as well as a function that uses it. -type Handler struct { - Instance *Instance - Endpoint types.Endpoint - Method string - Handler func(context.Context, *Instance, http.ResponseWriter, *http.Request) (int, error) -} - -// Path returns a path that should be configured for this handler -func (h Handler) Path() string { - return h.Endpoint.Path("", h.Instance.LogParameters.Prefix) -} - -// ServeHTTP is part of the http.Handler interface -func (a Handler) ServeHTTP(w http.ResponseWriter, r *http.Request) { - // export prometheus metrics - var now time.Time = time.Now() - var statusCode int - defer func() { - rspcnt.Inc(a.Instance.LogParameters.LogId, string(a.Endpoint), fmt.Sprintf("%d", statusCode)) - latency.Observe(time.Now().Sub(now).Seconds(), a.Instance.LogParameters.LogId, string(a.Endpoint), fmt.Sprintf("%d", statusCode)) - }() - reqcnt.Inc(a.Instance.LogParameters.LogId, string(a.Endpoint)) - - ctx, cancel := context.WithDeadline(r.Context(), now.Add(a.Instance.LogParameters.Deadline)) - defer cancel() - - if r.Method != a.Method { - glog.Warningf("%s/%s: got HTTP %s, wanted HTTP %s", a.Instance.LogParameters.Prefix, string(a.Endpoint), r.Method, a.Method) - http.Error(w, "", http.StatusMethodNotAllowed) - return - } - - statusCode, err := a.Handler(ctx, a.Instance, w, r) - if err != nil { - glog.Warningf("handler error %s/%s: %v", a.Instance.LogParameters.Prefix, a.Endpoint, err) - http.Error(w, fmt.Sprintf("%s%s%s%s", "Error", types.Delim, err.Error(), types.EOL), statusCode) - } -} diff --git a/instance_test.go b/instance_test.go deleted file mode 100644 index de539a1..0000000 --- a/instance_test.go +++ /dev/null @@ -1,159 +0,0 @@ -package stfe - -import ( - "crypto" - "testing" - - "net/http" - "net/http/httptest" - - "github.com/golang/mock/gomock" - "github.com/google/certificate-transparency-go/trillian/mockclient" - "github.com/system-transparency/stfe/testdata" - "github.com/system-transparency/stfe/types" -) - -type testInstance struct { - ctrl *gomock.Controller - client *mockclient.MockTrillianLogClient - instance *Instance -} - -// newTestInstances sets up a test instance that uses default log parameters -// with an optional signer, see newLogParameters() for further details. The -// SthSource is instantiated with an ActiveSthSource that has (i) the default -// STH as the currently cosigned STH based on testdata.Ed25519VkWitness, and -// (ii) the default STH without any cosignatures as the currently stable STH. -func newTestInstance(t *testing.T, signer crypto.Signer) *testInstance { - t.Helper() - ctrl := gomock.NewController(t) - client := mockclient.NewMockTrillianLogClient(ctrl) - return &testInstance{ - ctrl: ctrl, - client: client, - instance: &Instance{ - Client: client, - LogParameters: newLogParameters(t, signer), - SthSource: &ActiveSthSource{ - client: client, - logParameters: newLogParameters(t, signer), - currCosth: testdata.DefaultCosth(t, testdata.Ed25519VkLog, [][32]byte{testdata.Ed25519VkWitness}), - nextCosth: testdata.DefaultCosth(t, testdata.Ed25519VkLog, nil), - cosignatureFrom: make(map[[types.NamespaceFingerprintSize]byte]bool), - }, - }, - } -} - -// getHandlers returns all endpoints that use HTTP GET as a map to handlers -func (ti *testInstance) getHandlers(t *testing.T) map[Endpoint]Handler { - t.Helper() - return map[Endpoint]Handler{ - EndpointGetLatestSth: Handler{Instance: ti.instance, Handler: getLatestSth, Endpoint: EndpointGetLatestSth, Method: http.MethodGet}, - EndpointGetStableSth: Handler{Instance: ti.instance, Handler: getStableSth, Endpoint: EndpointGetStableSth, Method: http.MethodGet}, - EndpointGetCosignedSth: Handler{Instance: ti.instance, Handler: getCosignedSth, Endpoint: EndpointGetCosignedSth, Method: http.MethodGet}, - } -} - -// postHandlers returns all endpoints that use HTTP POST as a map to handlers -func (ti *testInstance) postHandlers(t *testing.T) map[Endpoint]Handler { - t.Helper() - return map[Endpoint]Handler{ - EndpointAddEntry: Handler{Instance: ti.instance, Handler: addEntry, Endpoint: EndpointAddEntry, Method: http.MethodPost}, - EndpointAddCosignature: Handler{Instance: ti.instance, Handler: addCosignature, Endpoint: EndpointAddCosignature, Method: http.MethodPost}, - EndpointGetConsistencyProof: Handler{Instance: ti.instance, Handler: getConsistencyProof, Endpoint: EndpointGetConsistencyProof, Method: http.MethodPost}, - EndpointGetProofByHash: Handler{Instance: ti.instance, Handler: getProofByHash, Endpoint: EndpointGetProofByHash, Method: http.MethodPost}, - EndpointGetEntries: Handler{Instance: ti.instance, Handler: getEntries, Endpoint: EndpointGetEntries, Method: http.MethodPost}, - } -} - -// getHandler must return a particular HTTP GET handler -func (ti *testInstance) getHandler(t *testing.T, endpoint Endpoint) Handler { - t.Helper() - handler, ok := ti.getHandlers(t)[endpoint] - if !ok { - t.Fatalf("must return HTTP GET handler for endpoint: %s", endpoint) - } - return handler -} - -// postHandler must return a particular HTTP POST handler -func (ti *testInstance) postHandler(t *testing.T, endpoint Endpoint) Handler { - t.Helper() - handler, ok := ti.postHandlers(t)[endpoint] - if !ok { - t.Fatalf("must return HTTP POST handler for endpoint: %s", endpoint) - } - return handler -} - -// TestHandlers checks that we configured all endpoints and that there are no -// unexpected ones. -func TestHandlers(t *testing.T) { - endpoints := map[Endpoint]bool{ - EndpointAddEntry: false, - EndpointAddCosignature: false, - EndpointGetLatestSth: false, - EndpointGetStableSth: false, - EndpointGetCosignedSth: false, - EndpointGetConsistencyProof: false, - EndpointGetProofByHash: false, - EndpointGetEntries: false, - } - i := &Instance{nil, newLogParameters(t, nil), nil} - for _, handler := range i.Handlers() { - if _, ok := endpoints[handler.Endpoint]; !ok { - t.Errorf("got unexpected endpoint: %s", handler.Endpoint) - } - endpoints[handler.Endpoint] = true - } - for endpoint, ok := range endpoints { - if !ok { - t.Errorf("endpoint %s is not configured", endpoint) - } - } -} - -// TestGetHandlersRejectPost checks that all get handlers reject post requests -func TestGetHandlersRejectPost(t *testing.T) { - ti := newTestInstance(t, nil) - defer ti.ctrl.Finish() - - for endpoint, handler := range ti.getHandlers(t) { - t.Run(string(endpoint), func(t *testing.T) { - s := httptest.NewServer(handler) - defer s.Close() - - url := endpoint.Path(s.URL, ti.instance.LogParameters.Prefix) - if rsp, err := http.Post(url, "application/json", nil); err != nil { - t.Fatalf("http.Post(%s)=(_,%q), want (_,nil)", url, err) - } else if rsp.StatusCode != http.StatusMethodNotAllowed { - t.Errorf("http.Post(%s)=(%d,nil), want (%d, nil)", url, rsp.StatusCode, http.StatusMethodNotAllowed) - } - }) - } -} - -// TestPostHandlersRejectGet checks that all post handlers reject get requests -func TestPostHandlersRejectGet(t *testing.T) { - ti := newTestInstance(t, nil) - defer ti.ctrl.Finish() - - for endpoint, handler := range ti.postHandlers(t) { - t.Run(string(endpoint), func(t *testing.T) { - s := httptest.NewServer(handler) - defer s.Close() - - url := endpoint.Path(s.URL, ti.instance.LogParameters.Prefix) - if rsp, err := http.Get(url); err != nil { - t.Fatalf("http.Get(%s)=(_,%q), want (_,nil)", url, err) - } else if rsp.StatusCode != http.StatusMethodNotAllowed { - t.Errorf("http.Get(%s)=(%d,nil), want (%d, nil)", url, rsp.StatusCode, http.StatusMethodNotAllowed) - } - }) - } -} - -// TODO: TestHandlerPath -func TestHandlerPath(t *testing.T) { -} diff --git a/log_parameters.go b/log_parameters.go deleted file mode 100644 index aceff3e..0000000 --- a/log_parameters.go +++ /dev/null @@ -1,47 +0,0 @@ -package stfe - -import ( - "crypto" - "crypto/ed25519" - "fmt" - "time" - - "github.com/system-transparency/stfe/types" -) - -// LogParameters is a collection of log parameters -type LogParameters struct { - LogId string // serialized log id (hex) - TreeId int64 // used internally by Trillian - Prefix string // e.g., "test" for /test - MaxRange int64 // max entries per get-entries request - Deadline time.Duration // gRPC deadline - Interval time.Duration // cosigning sth frequency - HashType crypto.Hash // hash function used by Trillian - Signer crypto.Signer // access to Ed25519 private key - - // Witnesses map trusted witness identifiers to public verification keys - Witnesses map[[types.HashSize]byte][types.VerificationKeySize]byte -} - -// Sign signs a tree head -func (lp *LogParameters) Sign(th *types.TreeHead) (*types.SignedTreeHead, error) { - sig, err := lp.Signer.Sign(nil, th.Marshal(), crypto.Hash(0)) - if err != nil { - return nil, fmt.Errorf("Sign failed: %v", err) - } - lastSthTimestamp.Set(float64(time.Now().Unix()), lp.LogId) - lastSthSize.Set(float64(th.TreeSize), lp.LogId) - - sigident := types.SigIdent{ - KeyHash: types.Hash(lp.Signer.Public().(ed25519.PublicKey)[:]), - Signature: &[types.SignatureSize]byte{}, - } - copy(sigident.Signature[:], sig) - return &types.SignedTreeHead{ - TreeHead: *th, - SigIdent: []*types.SigIdent{ - &sigident, - }, - }, nil -} diff --git a/log_parameters_test.go b/log_parameters_test.go deleted file mode 100644 index 88e83ad..0000000 --- a/log_parameters_test.go +++ /dev/null @@ -1,99 +0,0 @@ -package stfe - -import ( - "crypto" - "fmt" - "reflect" - "testing" - - cttestdata "github.com/google/certificate-transparency-go/trillian/testdata" - "github.com/system-transparency/stfe/testdata" - "github.com/system-transparency/stfe/types" -) - -// newLogParameters must create new log parameters with an optional log signer -// based on the parameters in "github.com/system-transparency/stfe/testdata". -// The log's namespace is initialized with testdata.LogEd25519Vk, the submmiter -// namespace list is initialized with testdata.SubmmiterEd25519, and the witness -// namespace list is initialized with testdata.WitnessEd25519Vk. The log's -// submitter and witness policies are set to reject unregistered namespace. -func newLogParameters(t *testing.T, signer crypto.Signer) *LogParameters { - t.Helper() - logId := testdata.NewNamespace(t, testdata.Ed25519VkLog) - witnessPool := testdata.NewNamespacePool(t, []*types.Namespace{ - testdata.NewNamespace(t, testdata.Ed25519VkWitness), - }) - submitPool := testdata.NewNamespacePool(t, []*types.Namespace{ - testdata.NewNamespace(t, testdata.Ed25519VkSubmitter), - }) - lp, err := NewLogParameters(signer, logId, testdata.TreeId, testdata.Prefix, submitPool, witnessPool, testdata.MaxRange, testdata.Interval, testdata.Deadline, true, true) - if err != nil { - t.Fatalf("must create new log parameters: %v", err) - } - return lp -} - -func TestNewLogParameters(t *testing.T) { - for _, table := range []struct { - description string - logId *types.Namespace - wantErr bool - }{ - { - description: "invalid: cannot marshal log id", - logId: &types.Namespace{ - Format: types.NamespaceFormatReserved, - }, - wantErr: true, - }, - { - description: "valid", - logId: testdata.NewNamespace(t, testdata.Ed25519VkLog), - }, - } { - _, err := NewLogParameters(nil, table.logId, testdata.TreeId, testdata.Prefix, nil, nil, testdata.MaxRange, testdata.Interval, testdata.Deadline, true, true) - if got, want := err != nil, table.wantErr; got != want { - t.Errorf("got error %v but wanted %v in test %q: %v", got, want, table.description, err) - } - } -} - -func TestSignTreeHeadV1(t *testing.T) { - for _, table := range []struct { - description string - th *types.TreeHeadV1 - signer crypto.Signer - wantErr bool - wantSth *types.StItem - }{ - { - description: "invalid: marshal failure", - th: types.NewTreeHeadV1(testdata.Timestamp, testdata.TreeSize, nil, testdata.Extension), - wantErr: true, - }, - { - description: "invalid: signature failure", - th: types.NewTreeHeadV1(testdata.Timestamp, testdata.TreeSize, testdata.NodeHash, testdata.Extension), - signer: cttestdata.NewSignerWithErr(nil, fmt.Errorf("signer failed")), - wantErr: true, - }, - { - description: "valid", - th: testdata.DefaultTh(t), - signer: cttestdata.NewSignerWithFixedSig(nil, testdata.Signature), - wantSth: testdata.DefaultSth(t, testdata.Ed25519VkLog), - }, - } { - sth, err := newLogParameters(t, table.signer).SignTreeHeadV1(table.th) - if got, want := err != nil, table.wantErr; got != want { - t.Errorf("got error %v but wanted %v in test %q: %v", got, want, table.description, err) - } - if err != nil { - continue - } - - if got, want := sth, table.wantSth; !reflect.DeepEqual(got, want) { - t.Errorf("got \n%v\n\tbut wanted\n%v\n\tin test %q", got, want, table.description) - } - } -} diff --git a/metric.go b/metric.go deleted file mode 100644 index 7e3e8b2..0000000 --- a/metric.go +++ /dev/null @@ -1,23 +0,0 @@ -package stfe - -import ( - "github.com/google/trillian/monitoring" - "github.com/google/trillian/monitoring/prometheus" -) - -var ( - reqcnt monitoring.Counter // number of incoming http requests - rspcnt monitoring.Counter // number of valid http responses - latency monitoring.Histogram // request-response latency - lastSthTimestamp monitoring.Gauge // unix timestamp from the most recent sth - lastSthSize monitoring.Gauge // tree size of most recent sth -) - -func init() { - mf := prometheus.MetricFactory{} - reqcnt = mf.NewCounter("http_req", "number of http requests", "logid", "endpoint") - rspcnt = mf.NewCounter("http_rsp", "number of http requests", "logid", "endpoint", "status") - latency = mf.NewHistogram("http_latency", "http request-response latency", "logid", "endpoint", "status") - lastSthTimestamp = mf.NewGauge("last_sth_timestamp", "unix timestamp while handling the most recent sth", "logid") - lastSthSize = mf.NewGauge("last_sth_size", "most recent sth tree size", "logid") -} diff --git a/mocks/crypto.go b/mocks/crypto.go deleted file mode 100644 index 87c883a..0000000 --- a/mocks/crypto.go +++ /dev/null @@ -1,23 +0,0 @@ -package mocks - -import ( - "crypto" - "crypto/ed25519" - "io" -) - -// TestSign implements the signer interface. It can be used to mock an Ed25519 -// signer that always return the same public key, signature, and error. -type TestSigner struct { - PublicKey *[ed25519.PublicKeySize]byte - Signature *[ed25519.SignatureSize]byte - Error error -} - -func (ts *TestSigner) Public() crypto.PublicKey { - return ed25519.PublicKey(ts.PublicKey[:]) -} - -func (ts *TestSigner) Sign(rand io.Reader, digest []byte, opts crypto.SignerOpts) ([]byte, error) { - return ts.Signature[:], ts.Error -} diff --git a/mocks/stfe.go b/mocks/stfe.go deleted file mode 100644 index e0fe7a9..0000000 --- a/mocks/stfe.go +++ /dev/null @@ -1,110 +0,0 @@ -// Code generated by MockGen. DO NOT EDIT. -// Source: github.com/system-transparency/stfe/trillian (interfaces: Client) - -// Package mocks is a generated GoMock package. -package mocks - -import ( - context "context" - reflect "reflect" - - gomock "github.com/golang/mock/gomock" - types "github.com/system-transparency/stfe/types" -) - -// MockClient is a mock of Client interface. -type MockClient struct { - ctrl *gomock.Controller - recorder *MockClientMockRecorder -} - -// MockClientMockRecorder is the mock recorder for MockClient. -type MockClientMockRecorder struct { - mock *MockClient -} - -// NewMockClient creates a new mock instance. -func NewMockClient(ctrl *gomock.Controller) *MockClient { - mock := &MockClient{ctrl: ctrl} - mock.recorder = &MockClientMockRecorder{mock} - return mock -} - -// EXPECT returns an object that allows the caller to indicate expected use. -func (m *MockClient) EXPECT() *MockClientMockRecorder { - return m.recorder -} - -// AddLeaf mocks base method. -func (m *MockClient) AddLeaf(arg0 context.Context, arg1 *types.LeafRequest) error { - m.ctrl.T.Helper() - ret := m.ctrl.Call(m, "AddLeaf", arg0, arg1) - ret0, _ := ret[0].(error) - return ret0 -} - -// AddLeaf indicates an expected call of AddLeaf. -func (mr *MockClientMockRecorder) AddLeaf(arg0, arg1 interface{}) *gomock.Call { - mr.mock.ctrl.T.Helper() - return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "AddLeaf", reflect.TypeOf((*MockClient)(nil).AddLeaf), arg0, arg1) -} - -// GetConsistencyProof mocks base method. -func (m *MockClient) GetConsistencyProof(arg0 context.Context, arg1 *types.ConsistencyProofRequest) (*types.ConsistencyProof, error) { - m.ctrl.T.Helper() - ret := m.ctrl.Call(m, "GetConsistencyProof", arg0, arg1) - ret0, _ := ret[0].(*types.ConsistencyProof) - ret1, _ := ret[1].(error) - return ret0, ret1 -} - -// GetConsistencyProof indicates an expected call of GetConsistencyProof. -func (mr *MockClientMockRecorder) GetConsistencyProof(arg0, arg1 interface{}) *gomock.Call { - mr.mock.ctrl.T.Helper() - return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetConsistencyProof", reflect.TypeOf((*MockClient)(nil).GetConsistencyProof), arg0, arg1) -} - -// GetInclusionProof mocks base method. -func (m *MockClient) GetInclusionProof(arg0 context.Context, arg1 *types.InclusionProofRequest) (*types.InclusionProof, error) { - m.ctrl.T.Helper() - ret := m.ctrl.Call(m, "GetInclusionProof", arg0, arg1) - ret0, _ := ret[0].(*types.InclusionProof) - ret1, _ := ret[1].(error) - return ret0, ret1 -} - -// GetInclusionProof indicates an expected call of GetInclusionProof. -func (mr *MockClientMockRecorder) GetInclusionProof(arg0, arg1 interface{}) *gomock.Call { - mr.mock.ctrl.T.Helper() - return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetInclusionProof", reflect.TypeOf((*MockClient)(nil).GetInclusionProof), arg0, arg1) -} - -// GetLeaves mocks base method. -func (m *MockClient) GetLeaves(arg0 context.Context, arg1 *types.LeavesRequest) (*types.LeafList, error) { - m.ctrl.T.Helper() - ret := m.ctrl.Call(m, "GetLeaves", arg0, arg1) - ret0, _ := ret[0].(*types.LeafList) - ret1, _ := ret[1].(error) - return ret0, ret1 -} - -// GetLeaves indicates an expected call of GetLeaves. -func (mr *MockClientMockRecorder) GetLeaves(arg0, arg1 interface{}) *gomock.Call { - mr.mock.ctrl.T.Helper() - return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetLeaves", reflect.TypeOf((*MockClient)(nil).GetLeaves), arg0, arg1) -} - -// GetTreeHead mocks base method. -func (m *MockClient) GetTreeHead(arg0 context.Context) (*types.TreeHead, error) { - m.ctrl.T.Helper() - ret := m.ctrl.Call(m, "GetTreeHead", arg0) - ret0, _ := ret[0].(*types.TreeHead) - ret1, _ := ret[1].(error) - return ret0, ret1 -} - -// GetTreeHead indicates an expected call of GetTreeHead. -func (mr *MockClientMockRecorder) GetTreeHead(arg0 interface{}) *gomock.Call { - mr.mock.ctrl.T.Helper() - return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetTreeHead", reflect.TypeOf((*MockClient)(nil).GetTreeHead), arg0) -} diff --git a/mocks/trillian.go b/mocks/trillian.go deleted file mode 100644 index 8aa3a58..0000000 --- a/mocks/trillian.go +++ /dev/null @@ -1,317 +0,0 @@ -// Code generated by MockGen. DO NOT EDIT. -// Source: github.com/google/trillian (interfaces: TrillianLogClient) - -// Package mocks is a generated GoMock package. -package mocks - -import ( - context "context" - reflect "reflect" - - gomock "github.com/golang/mock/gomock" - trillian "github.com/google/trillian" - grpc "google.golang.org/grpc" -) - -// MockTrillianLogClient is a mock of TrillianLogClient interface. -type MockTrillianLogClient struct { - ctrl *gomock.Controller - recorder *MockTrillianLogClientMockRecorder -} - -// MockTrillianLogClientMockRecorder is the mock recorder for MockTrillianLogClient. -type MockTrillianLogClientMockRecorder struct { - mock *MockTrillianLogClient -} - -// NewMockTrillianLogClient creates a new mock instance. -func NewMockTrillianLogClient(ctrl *gomock.Controller) *MockTrillianLogClient { - mock := &MockTrillianLogClient{ctrl: ctrl} - mock.recorder = &MockTrillianLogClientMockRecorder{mock} - return mock -} - -// EXPECT returns an object that allows the caller to indicate expected use. -func (m *MockTrillianLogClient) EXPECT() *MockTrillianLogClientMockRecorder { - return m.recorder -} - -// AddSequencedLeaf mocks base method. -func (m *MockTrillianLogClient) AddSequencedLeaf(arg0 context.Context, arg1 *trillian.AddSequencedLeafRequest, arg2 ...grpc.CallOption) (*trillian.AddSequencedLeafResponse, error) { - m.ctrl.T.Helper() - varargs := []interface{}{arg0, arg1} - for _, a := range arg2 { - varargs = append(varargs, a) - } - ret := m.ctrl.Call(m, "AddSequencedLeaf", varargs...) - ret0, _ := ret[0].(*trillian.AddSequencedLeafResponse) - ret1, _ := ret[1].(error) - return ret0, ret1 -} - -// AddSequencedLeaf indicates an expected call of AddSequencedLeaf. -func (mr *MockTrillianLogClientMockRecorder) AddSequencedLeaf(arg0, arg1 interface{}, arg2 ...interface{}) *gomock.Call { - mr.mock.ctrl.T.Helper() - varargs := append([]interface{}{arg0, arg1}, arg2...) - return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "AddSequencedLeaf", reflect.TypeOf((*MockTrillianLogClient)(nil).AddSequencedLeaf), varargs...) -} - -// AddSequencedLeaves mocks base method. -func (m *MockTrillianLogClient) AddSequencedLeaves(arg0 context.Context, arg1 *trillian.AddSequencedLeavesRequest, arg2 ...grpc.CallOption) (*trillian.AddSequencedLeavesResponse, error) { - m.ctrl.T.Helper() - varargs := []interface{}{arg0, arg1} - for _, a := range arg2 { - varargs = append(varargs, a) - } - ret := m.ctrl.Call(m, "AddSequencedLeaves", varargs...) - ret0, _ := ret[0].(*trillian.AddSequencedLeavesResponse) - ret1, _ := ret[1].(error) - return ret0, ret1 -} - -// AddSequencedLeaves indicates an expected call of AddSequencedLeaves. -func (mr *MockTrillianLogClientMockRecorder) AddSequencedLeaves(arg0, arg1 interface{}, arg2 ...interface{}) *gomock.Call { - mr.mock.ctrl.T.Helper() - varargs := append([]interface{}{arg0, arg1}, arg2...) - return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "AddSequencedLeaves", reflect.TypeOf((*MockTrillianLogClient)(nil).AddSequencedLeaves), varargs...) -} - -// GetConsistencyProof mocks base method. -func (m *MockTrillianLogClient) GetConsistencyProof(arg0 context.Context, arg1 *trillian.GetConsistencyProofRequest, arg2 ...grpc.CallOption) (*trillian.GetConsistencyProofResponse, error) { - m.ctrl.T.Helper() - varargs := []interface{}{arg0, arg1} - for _, a := range arg2 { - varargs = append(varargs, a) - } - ret := m.ctrl.Call(m, "GetConsistencyProof", varargs...) - ret0, _ := ret[0].(*trillian.GetConsistencyProofResponse) - ret1, _ := ret[1].(error) - return ret0, ret1 -} - -// GetConsistencyProof indicates an expected call of GetConsistencyProof. -func (mr *MockTrillianLogClientMockRecorder) GetConsistencyProof(arg0, arg1 interface{}, arg2 ...interface{}) *gomock.Call { - mr.mock.ctrl.T.Helper() - varargs := append([]interface{}{arg0, arg1}, arg2...) - return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetConsistencyProof", reflect.TypeOf((*MockTrillianLogClient)(nil).GetConsistencyProof), varargs...) -} - -// GetEntryAndProof mocks base method. -func (m *MockTrillianLogClient) GetEntryAndProof(arg0 context.Context, arg1 *trillian.GetEntryAndProofRequest, arg2 ...grpc.CallOption) (*trillian.GetEntryAndProofResponse, error) { - m.ctrl.T.Helper() - varargs := []interface{}{arg0, arg1} - for _, a := range arg2 { - varargs = append(varargs, a) - } - ret := m.ctrl.Call(m, "GetEntryAndProof", varargs...) - ret0, _ := ret[0].(*trillian.GetEntryAndProofResponse) - ret1, _ := ret[1].(error) - return ret0, ret1 -} - -// GetEntryAndProof indicates an expected call of GetEntryAndProof. -func (mr *MockTrillianLogClientMockRecorder) GetEntryAndProof(arg0, arg1 interface{}, arg2 ...interface{}) *gomock.Call { - mr.mock.ctrl.T.Helper() - varargs := append([]interface{}{arg0, arg1}, arg2...) - return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetEntryAndProof", reflect.TypeOf((*MockTrillianLogClient)(nil).GetEntryAndProof), varargs...) -} - -// GetInclusionProof mocks base method. -func (m *MockTrillianLogClient) GetInclusionProof(arg0 context.Context, arg1 *trillian.GetInclusionProofRequest, arg2 ...grpc.CallOption) (*trillian.GetInclusionProofResponse, error) { - m.ctrl.T.Helper() - varargs := []interface{}{arg0, arg1} - for _, a := range arg2 { - varargs = append(varargs, a) - } - ret := m.ctrl.Call(m, "GetInclusionProof", varargs...) - ret0, _ := ret[0].(*trillian.GetInclusionProofResponse) - ret1, _ := ret[1].(error) - return ret0, ret1 -} - -// GetInclusionProof indicates an expected call of GetInclusionProof. -func (mr *MockTrillianLogClientMockRecorder) GetInclusionProof(arg0, arg1 interface{}, arg2 ...interface{}) *gomock.Call { - mr.mock.ctrl.T.Helper() - varargs := append([]interface{}{arg0, arg1}, arg2...) - return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetInclusionProof", reflect.TypeOf((*MockTrillianLogClient)(nil).GetInclusionProof), varargs...) -} - -// GetInclusionProofByHash mocks base method. -func (m *MockTrillianLogClient) GetInclusionProofByHash(arg0 context.Context, arg1 *trillian.GetInclusionProofByHashRequest, arg2 ...grpc.CallOption) (*trillian.GetInclusionProofByHashResponse, error) { - m.ctrl.T.Helper() - varargs := []interface{}{arg0, arg1} - for _, a := range arg2 { - varargs = append(varargs, a) - } - ret := m.ctrl.Call(m, "GetInclusionProofByHash", varargs...) - ret0, _ := ret[0].(*trillian.GetInclusionProofByHashResponse) - ret1, _ := ret[1].(error) - return ret0, ret1 -} - -// GetInclusionProofByHash indicates an expected call of GetInclusionProofByHash. -func (mr *MockTrillianLogClientMockRecorder) GetInclusionProofByHash(arg0, arg1 interface{}, arg2 ...interface{}) *gomock.Call { - mr.mock.ctrl.T.Helper() - varargs := append([]interface{}{arg0, arg1}, arg2...) - return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetInclusionProofByHash", reflect.TypeOf((*MockTrillianLogClient)(nil).GetInclusionProofByHash), varargs...) -} - -// GetLatestSignedLogRoot mocks base method. -func (m *MockTrillianLogClient) GetLatestSignedLogRoot(arg0 context.Context, arg1 *trillian.GetLatestSignedLogRootRequest, arg2 ...grpc.CallOption) (*trillian.GetLatestSignedLogRootResponse, error) { - m.ctrl.T.Helper() - varargs := []interface{}{arg0, arg1} - for _, a := range arg2 { - varargs = append(varargs, a) - } - ret := m.ctrl.Call(m, "GetLatestSignedLogRoot", varargs...) - ret0, _ := ret[0].(*trillian.GetLatestSignedLogRootResponse) - ret1, _ := ret[1].(error) - return ret0, ret1 -} - -// GetLatestSignedLogRoot indicates an expected call of GetLatestSignedLogRoot. -func (mr *MockTrillianLogClientMockRecorder) GetLatestSignedLogRoot(arg0, arg1 interface{}, arg2 ...interface{}) *gomock.Call { - mr.mock.ctrl.T.Helper() - varargs := append([]interface{}{arg0, arg1}, arg2...) - return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetLatestSignedLogRoot", reflect.TypeOf((*MockTrillianLogClient)(nil).GetLatestSignedLogRoot), varargs...) -} - -// GetLeavesByHash mocks base method. -func (m *MockTrillianLogClient) GetLeavesByHash(arg0 context.Context, arg1 *trillian.GetLeavesByHashRequest, arg2 ...grpc.CallOption) (*trillian.GetLeavesByHashResponse, error) { - m.ctrl.T.Helper() - varargs := []interface{}{arg0, arg1} - for _, a := range arg2 { - varargs = append(varargs, a) - } - ret := m.ctrl.Call(m, "GetLeavesByHash", varargs...) - ret0, _ := ret[0].(*trillian.GetLeavesByHashResponse) - ret1, _ := ret[1].(error) - return ret0, ret1 -} - -// GetLeavesByHash indicates an expected call of GetLeavesByHash. -func (mr *MockTrillianLogClientMockRecorder) GetLeavesByHash(arg0, arg1 interface{}, arg2 ...interface{}) *gomock.Call { - mr.mock.ctrl.T.Helper() - varargs := append([]interface{}{arg0, arg1}, arg2...) - return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetLeavesByHash", reflect.TypeOf((*MockTrillianLogClient)(nil).GetLeavesByHash), varargs...) -} - -// GetLeavesByIndex mocks base method. -func (m *MockTrillianLogClient) GetLeavesByIndex(arg0 context.Context, arg1 *trillian.GetLeavesByIndexRequest, arg2 ...grpc.CallOption) (*trillian.GetLeavesByIndexResponse, error) { - m.ctrl.T.Helper() - varargs := []interface{}{arg0, arg1} - for _, a := range arg2 { - varargs = append(varargs, a) - } - ret := m.ctrl.Call(m, "GetLeavesByIndex", varargs...) - ret0, _ := ret[0].(*trillian.GetLeavesByIndexResponse) - ret1, _ := ret[1].(error) - return ret0, ret1 -} - -// GetLeavesByIndex indicates an expected call of GetLeavesByIndex. -func (mr *MockTrillianLogClientMockRecorder) GetLeavesByIndex(arg0, arg1 interface{}, arg2 ...interface{}) *gomock.Call { - mr.mock.ctrl.T.Helper() - varargs := append([]interface{}{arg0, arg1}, arg2...) - return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetLeavesByIndex", reflect.TypeOf((*MockTrillianLogClient)(nil).GetLeavesByIndex), varargs...) -} - -// GetLeavesByRange mocks base method. -func (m *MockTrillianLogClient) GetLeavesByRange(arg0 context.Context, arg1 *trillian.GetLeavesByRangeRequest, arg2 ...grpc.CallOption) (*trillian.GetLeavesByRangeResponse, error) { - m.ctrl.T.Helper() - varargs := []interface{}{arg0, arg1} - for _, a := range arg2 { - varargs = append(varargs, a) - } - ret := m.ctrl.Call(m, "GetLeavesByRange", varargs...) - ret0, _ := ret[0].(*trillian.GetLeavesByRangeResponse) - ret1, _ := ret[1].(error) - return ret0, ret1 -} - -// GetLeavesByRange indicates an expected call of GetLeavesByRange. -func (mr *MockTrillianLogClientMockRecorder) GetLeavesByRange(arg0, arg1 interface{}, arg2 ...interface{}) *gomock.Call { - mr.mock.ctrl.T.Helper() - varargs := append([]interface{}{arg0, arg1}, arg2...) - return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetLeavesByRange", reflect.TypeOf((*MockTrillianLogClient)(nil).GetLeavesByRange), varargs...) -} - -// GetSequencedLeafCount mocks base method. -func (m *MockTrillianLogClient) GetSequencedLeafCount(arg0 context.Context, arg1 *trillian.GetSequencedLeafCountRequest, arg2 ...grpc.CallOption) (*trillian.GetSequencedLeafCountResponse, error) { - m.ctrl.T.Helper() - varargs := []interface{}{arg0, arg1} - for _, a := range arg2 { - varargs = append(varargs, a) - } - ret := m.ctrl.Call(m, "GetSequencedLeafCount", varargs...) - ret0, _ := ret[0].(*trillian.GetSequencedLeafCountResponse) - ret1, _ := ret[1].(error) - return ret0, ret1 -} - -// GetSequencedLeafCount indicates an expected call of GetSequencedLeafCount. -func (mr *MockTrillianLogClientMockRecorder) GetSequencedLeafCount(arg0, arg1 interface{}, arg2 ...interface{}) *gomock.Call { - mr.mock.ctrl.T.Helper() - varargs := append([]interface{}{arg0, arg1}, arg2...) - return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetSequencedLeafCount", reflect.TypeOf((*MockTrillianLogClient)(nil).GetSequencedLeafCount), varargs...) -} - -// InitLog mocks base method. -func (m *MockTrillianLogClient) InitLog(arg0 context.Context, arg1 *trillian.InitLogRequest, arg2 ...grpc.CallOption) (*trillian.InitLogResponse, error) { - m.ctrl.T.Helper() - varargs := []interface{}{arg0, arg1} - for _, a := range arg2 { - varargs = append(varargs, a) - } - ret := m.ctrl.Call(m, "InitLog", varargs...) - ret0, _ := ret[0].(*trillian.InitLogResponse) - ret1, _ := ret[1].(error) - return ret0, ret1 -} - -// InitLog indicates an expected call of InitLog. -func (mr *MockTrillianLogClientMockRecorder) InitLog(arg0, arg1 interface{}, arg2 ...interface{}) *gomock.Call { - mr.mock.ctrl.T.Helper() - varargs := append([]interface{}{arg0, arg1}, arg2...) - return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "InitLog", reflect.TypeOf((*MockTrillianLogClient)(nil).InitLog), varargs...) -} - -// QueueLeaf mocks base method. -func (m *MockTrillianLogClient) QueueLeaf(arg0 context.Context, arg1 *trillian.QueueLeafRequest, arg2 ...grpc.CallOption) (*trillian.QueueLeafResponse, error) { - m.ctrl.T.Helper() - varargs := []interface{}{arg0, arg1} - for _, a := range arg2 { - varargs = append(varargs, a) - } - ret := m.ctrl.Call(m, "QueueLeaf", varargs...) - ret0, _ := ret[0].(*trillian.QueueLeafResponse) - ret1, _ := ret[1].(error) - return ret0, ret1 -} - -// QueueLeaf indicates an expected call of QueueLeaf. -func (mr *MockTrillianLogClientMockRecorder) QueueLeaf(arg0, arg1 interface{}, arg2 ...interface{}) *gomock.Call { - mr.mock.ctrl.T.Helper() - varargs := append([]interface{}{arg0, arg1}, arg2...) - return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "QueueLeaf", reflect.TypeOf((*MockTrillianLogClient)(nil).QueueLeaf), varargs...) -} - -// QueueLeaves mocks base method. -func (m *MockTrillianLogClient) QueueLeaves(arg0 context.Context, arg1 *trillian.QueueLeavesRequest, arg2 ...grpc.CallOption) (*trillian.QueueLeavesResponse, error) { - m.ctrl.T.Helper() - varargs := []interface{}{arg0, arg1} - for _, a := range arg2 { - varargs = append(varargs, a) - } - ret := m.ctrl.Call(m, "QueueLeaves", varargs...) - ret0, _ := ret[0].(*trillian.QueueLeavesResponse) - ret1, _ := ret[1].(error) - return ret0, ret1 -} - -// QueueLeaves indicates an expected call of QueueLeaves. -func (mr *MockTrillianLogClientMockRecorder) QueueLeaves(arg0, arg1 interface{}, arg2 ...interface{}) *gomock.Call { - mr.mock.ctrl.T.Helper() - varargs := append([]interface{}{arg0, arg1}, arg2...) - return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "QueueLeaves", reflect.TypeOf((*MockTrillianLogClient)(nil).QueueLeaves), varargs...) -} diff --git a/pkg/instance/endpoint.go b/pkg/instance/endpoint.go new file mode 100644 index 0000000..5085c49 --- /dev/null +++ b/pkg/instance/endpoint.go @@ -0,0 +1,122 @@ +package stfe + +import ( + "context" + "net/http" + + "github.com/golang/glog" +) + +func addLeaf(ctx context.Context, i *Instance, w http.ResponseWriter, r *http.Request) (int, error) { + glog.V(3).Info("handling add-entry request") + req, err := i.leafRequestFromHTTP(r) + if err != nil { + return http.StatusBadRequest, err + } + if err := i.Client.AddLeaf(ctx, req); err != nil { + return http.StatusInternalServerError, err + } + return http.StatusOK, nil +} + +func addCosignature(ctx context.Context, i *Instance, w http.ResponseWriter, r *http.Request) (int, error) { + glog.V(3).Info("handling add-cosignature request") + req, err := i.cosignatureRequestFromHTTP(r) + if err != nil { + return http.StatusBadRequest, err + } + vk := i.Witnesses[*req.KeyHash] + if err := i.Stateman.AddCosignature(ctx, &vk, req.Signature); err != nil { + return http.StatusBadRequest, err + } + return http.StatusOK, nil +} + +func getTreeHeadLatest(ctx context.Context, i *Instance, w http.ResponseWriter, _ *http.Request) (int, error) { + glog.V(3).Info("handling get-tree-head-latest request") + sth, err := i.Stateman.Latest(ctx) + if err != nil { + return http.StatusInternalServerError, err + } + if err := sth.MarshalASCII(w); err != nil { + return http.StatusInternalServerError, err + } + return http.StatusOK, nil +} + +func getTreeHeadToSign(ctx context.Context, i *Instance, w http.ResponseWriter, _ *http.Request) (int, error) { + glog.V(3).Info("handling get-tree-head-to-sign request") + sth, err := i.Stateman.ToSign(ctx) + if err != nil { + return http.StatusInternalServerError, err + } + if err := sth.MarshalASCII(w); err != nil { + return http.StatusInternalServerError, err + } + return http.StatusOK, nil +} + +func getTreeHeadCosigned(ctx context.Context, i *Instance, w http.ResponseWriter, _ *http.Request) (int, error) { + glog.V(3).Info("handling get-tree-head-cosigned request") + sth, err := i.Stateman.Cosigned(ctx) + if err != nil { + return http.StatusInternalServerError, err + } + if err := sth.MarshalASCII(w); err != nil { + return http.StatusInternalServerError, err + } + return http.StatusOK, nil +} + +func getConsistencyProof(ctx context.Context, i *Instance, w http.ResponseWriter, r *http.Request) (int, error) { + glog.V(3).Info("handling get-consistency-proof request") + req, err := i.consistencyProofRequestFromHTTP(r) + if err != nil { + return http.StatusBadRequest, err + } + + proof, err := i.Client.GetConsistencyProof(ctx, req) + if err != nil { + return http.StatusInternalServerError, err + } + if err := proof.MarshalASCII(w); err != nil { + return http.StatusInternalServerError, err + } + return http.StatusOK, nil +} + +func getInclusionProof(ctx context.Context, i *Instance, w http.ResponseWriter, r *http.Request) (int, error) { + glog.V(3).Info("handling get-proof-by-hash request") + req, err := i.inclusionProofRequestFromHTTP(r) + if err != nil { + return http.StatusBadRequest, err + } + + proof, err := i.Client.GetInclusionProof(ctx, req) + if err != nil { + return http.StatusInternalServerError, err + } + if err := proof.MarshalASCII(w); err != nil { + return http.StatusInternalServerError, err + } + return http.StatusOK, nil +} + +func getLeaves(ctx context.Context, i *Instance, w http.ResponseWriter, r *http.Request) (int, error) { + glog.V(3).Info("handling get-leaves request") + req, err := i.leavesRequestFromHTTP(r) + if err != nil { + return http.StatusBadRequest, err + } + + leaves, err := i.Client.GetLeaves(ctx, req) + if err != nil { + return http.StatusInternalServerError, err + } + for _, leaf := range *leaves { + if err := leaf.MarshalASCII(w); err != nil { + return http.StatusInternalServerError, err + } + } + return http.StatusOK, nil +} diff --git a/pkg/instance/endpoint_test.go b/pkg/instance/endpoint_test.go new file mode 100644 index 0000000..8511b8d --- /dev/null +++ b/pkg/instance/endpoint_test.go @@ -0,0 +1,480 @@ +package stfe + +import ( + "bytes" + "context" + "fmt" + "net/http" + "net/http/httptest" + "reflect" + "testing" + + "github.com/golang/mock/gomock" + cttestdata "github.com/google/certificate-transparency-go/trillian/testdata" + "github.com/google/trillian" + "github.com/system-transparency/stfe/pkg/testdata" + "github.com/system-transparency/stfe/pkg/types" +) + +func TestEndpointAddEntry(t *testing.T) { + for _, table := range []struct { + description string + breq *bytes.Buffer + trsp *trillian.QueueLeafResponse + terr error + wantCode int + }{ + { + description: "invalid: bad request: empty", + breq: bytes.NewBuffer(nil), + wantCode: http.StatusBadRequest, + }, + { + description: "invalid: bad Trillian response: error", + breq: testdata.AddSignedChecksumBuffer(t, testdata.Ed25519SkSubmitter, testdata.Ed25519VkSubmitter), + terr: fmt.Errorf("backend failure"), + wantCode: http.StatusInternalServerError, + }, + { + description: "valid", + breq: testdata.AddSignedChecksumBuffer(t, testdata.Ed25519SkSubmitter, testdata.Ed25519VkSubmitter), + trsp: testdata.DefaultTQlr(t, false), + wantCode: http.StatusOK, + }, + } { + func() { // run deferred functions at the end of each iteration + ti := newTestInstance(t, nil) + defer ti.ctrl.Finish() + + url := EndpointAddEntry.Path("http://example.com", ti.instance.LogParameters.Prefix) + req, err := http.NewRequest("POST", url, table.breq) + if err != nil { + t.Fatalf("must create http request: %v", err) + } + req.Header.Set("Content-Type", "application/octet-stream") + if table.trsp != nil || table.terr != nil { + ti.client.EXPECT().QueueLeaf(newDeadlineMatcher(), gomock.Any()).Return(table.trsp, table.terr) + } + + w := httptest.NewRecorder() + ti.postHandler(t, EndpointAddEntry).ServeHTTP(w, req) + if got, want := w.Code, table.wantCode; got != want { + t.Errorf("got error code %d but wanted %d in test %q", got, want, table.description) + } + }() + } +} + +func TestEndpointAddCosignature(t *testing.T) { + for _, table := range []struct { + description string + breq *bytes.Buffer + wantCode int + }{ + { + description: "invalid: bad request: empty", + breq: bytes.NewBuffer(nil), + wantCode: http.StatusBadRequest, + }, + { + description: "invalid: signed wrong sth", // newLogParameters() use testdata.Ed25519VkLog as default + breq: testdata.AddCosignatureBuffer(t, testdata.DefaultSth(t, testdata.Ed25519VkLog2), &testdata.Ed25519SkWitness, &testdata.Ed25519VkWitness), + wantCode: http.StatusBadRequest, + }, + { + description: "valid", + breq: testdata.AddCosignatureBuffer(t, testdata.DefaultSth(t, testdata.Ed25519VkLog), &testdata.Ed25519SkWitness, &testdata.Ed25519VkWitness), + wantCode: http.StatusOK, + }, + } { + func() { // run deferred functions at the end of each iteration + ti := newTestInstance(t, nil) + defer ti.ctrl.Finish() + + url := EndpointAddCosignature.Path("http://example.com", ti.instance.LogParameters.Prefix) + req, err := http.NewRequest("POST", url, table.breq) + if err != nil { + t.Fatalf("must create http request: %v", err) + } + req.Header.Set("Content-Type", "application/octet-stream") + + w := httptest.NewRecorder() + ti.postHandler(t, EndpointAddCosignature).ServeHTTP(w, req) + if got, want := w.Code, table.wantCode; got != want { + t.Errorf("got error code %d but wanted %d in test %q", got, want, table.description) + } + }() + } +} + +func TestEndpointGetLatestSth(t *testing.T) { + for _, table := range []struct { + description string + trsp *trillian.GetLatestSignedLogRootResponse + terr error + wantCode int + wantItem *types.StItem + }{ + { + description: "backend failure", + terr: fmt.Errorf("backend failure"), + wantCode: http.StatusInternalServerError, + }, + { + description: "valid", + trsp: testdata.DefaultTSlr(t), + wantCode: http.StatusOK, + wantItem: testdata.DefaultSth(t, testdata.Ed25519VkLog), + }, + } { + func() { // run deferred functions at the end of each iteration + ti := newTestInstance(t, cttestdata.NewSignerWithFixedSig(nil, testdata.Signature)) + ti.ctrl.Finish() + + // Setup and run client query + url := EndpointGetLatestSth.Path("http://example.com", ti.instance.LogParameters.Prefix) + req, err := http.NewRequest("GET", url, nil) + if err != nil { + t.Fatalf("must create http request: %v", err) + } + if table.trsp != nil || table.terr != nil { + ti.client.EXPECT().GetLatestSignedLogRoot(newDeadlineMatcher(), gomock.Any()).Return(table.trsp, table.terr) + } + + w := httptest.NewRecorder() + ti.getHandler(t, EndpointGetLatestSth).ServeHTTP(w, req) + if got, want := w.Code, table.wantCode; got != want { + t.Errorf("got error code %d but wanted %d in test %q", got, want, table.description) + } + if w.Code != http.StatusOK { + return + } + + var item types.StItem + if err := types.Unmarshal([]byte(w.Body.String()), &item); err != nil { + t.Errorf("valid response cannot be unmarshalled in test %q: %v", table.description, err) + } + if got, want := item, *table.wantItem; !reflect.DeepEqual(got, want) { + t.Errorf("got item\n%v\n\tbut wanted\n%v\n\tin test %q", got, want, table.description) + } + }() + } +} + +func TestEndpointGetStableSth(t *testing.T) { + for _, table := range []struct { + description string + useBadSource bool + wantCode int + wantItem *types.StItem + }{ + { + description: "invalid: sth source failure", + useBadSource: true, + wantCode: http.StatusInternalServerError, + }, + { + description: "valid", + wantCode: http.StatusOK, + wantItem: testdata.DefaultSth(t, testdata.Ed25519VkLog), + }, + } { + func() { // run deferred functions at the end of each iteration + ti := newTestInstance(t, nil) + ti.ctrl.Finish() + if table.useBadSource { + ti.instance.SthSource = &ActiveSthSource{} + } + + // Setup and run client query + url := EndpointGetStableSth.Path("http://example.com", ti.instance.LogParameters.Prefix) + req, err := http.NewRequest("GET", url, nil) + if err != nil { + t.Fatalf("must create http request: %v", err) + } + + w := httptest.NewRecorder() + ti.getHandler(t, EndpointGetStableSth).ServeHTTP(w, req) + if got, want := w.Code, table.wantCode; got != want { + t.Errorf("got error code %d but wanted %d in test %q", got, want, table.description) + } + if w.Code != http.StatusOK { + return + } + + var item types.StItem + if err := types.Unmarshal([]byte(w.Body.String()), &item); err != nil { + t.Errorf("valid response cannot be unmarshalled in test %q: %v", table.description, err) + } + if got, want := item, *table.wantItem; !reflect.DeepEqual(got, want) { + t.Errorf("got item\n%v\n\tbut wanted\n%v\n\tin test %q", got, want, table.description) + } + }() + } +} + +func TestEndpointGetCosignedSth(t *testing.T) { + for _, table := range []struct { + description string + useBadSource bool + wantCode int + wantItem *types.StItem + }{ + { + description: "invalid: sth source failure", + useBadSource: true, + wantCode: http.StatusInternalServerError, + }, + { + description: "valid", + wantCode: http.StatusOK, + wantItem: testdata.DefaultCosth(t, testdata.Ed25519VkLog, [][32]byte{testdata.Ed25519VkWitness}), + }, + } { + func() { // run deferred functions at the end of each iteration + ti := newTestInstance(t, nil) + ti.ctrl.Finish() + if table.useBadSource { + ti.instance.SthSource = &ActiveSthSource{} + } + + // Setup and run client query + url := EndpointGetCosignedSth.Path("http://example.com", ti.instance.LogParameters.Prefix) + req, err := http.NewRequest("GET", url, nil) + if err != nil { + t.Fatalf("must create http request: %v", err) + } + + w := httptest.NewRecorder() + ti.getHandler(t, EndpointGetCosignedSth).ServeHTTP(w, req) + if got, want := w.Code, table.wantCode; got != want { + t.Errorf("got error code %d but wanted %d in test %q", got, want, table.description) + } + if w.Code != http.StatusOK { + return + } + + var item types.StItem + if err := types.Unmarshal([]byte(w.Body.String()), &item); err != nil { + t.Errorf("valid response cannot be unmarshalled in test %q: %v", table.description, err) + } + if got, want := item, *table.wantItem; !reflect.DeepEqual(got, want) { + t.Errorf("got item\n%v\n\tbut wanted\n%v\n\tin test %q", got, want, table.description) + } + }() + } +} + +func TestEndpointGetProofByHash(t *testing.T) { + for _, table := range []struct { + description string + breq *bytes.Buffer + trsp *trillian.GetInclusionProofByHashResponse + terr error + wantCode int + wantItem *types.StItem + }{ + { + description: "invalid: bad request: empty", + breq: bytes.NewBuffer(nil), + wantCode: http.StatusBadRequest, + }, + { + description: "invalid: bad Trillian response: error", + breq: bytes.NewBuffer(marshal(t, types.GetProofByHashV1{TreeSize: 1, Hash: testdata.LeafHash})), + terr: fmt.Errorf("backend failure"), + wantCode: http.StatusInternalServerError, + }, + { + description: "valid", + breq: bytes.NewBuffer(marshal(t, types.GetProofByHashV1{TreeSize: 1, Hash: testdata.LeafHash})), + trsp: testdata.DefaultTGipbhr(t), + wantCode: http.StatusOK, + wantItem: testdata.DefaultInclusionProof(t, 1), + }, + } { + func() { // run deferred functions at the end of each iteration + ti := newTestInstance(t, nil) + defer ti.ctrl.Finish() + + url := EndpointGetProofByHash.Path("http://example.com", ti.instance.LogParameters.Prefix) + req, err := http.NewRequest("POST", url, table.breq) + if err != nil { + t.Fatalf("must create http request: %v", err) + } + req.Header.Set("Content-Type", "application/octet-stream") + if table.trsp != nil || table.terr != nil { + ti.client.EXPECT().GetInclusionProofByHash(newDeadlineMatcher(), gomock.Any()).Return(table.trsp, table.terr) + } + + w := httptest.NewRecorder() + ti.postHandler(t, EndpointGetProofByHash).ServeHTTP(w, req) + if got, want := w.Code, table.wantCode; got != want { + t.Errorf("got error code %d but wanted %d in test %q", got, want, table.description) + } + if w.Code != http.StatusOK { + return + } + + var item types.StItem + if err := types.Unmarshal([]byte(w.Body.String()), &item); err != nil { + t.Errorf("valid response cannot be unmarshalled in test %q: %v", table.description, err) + } + if got, want := item, *table.wantItem; !reflect.DeepEqual(got, want) { + t.Errorf("got item\n%v\n\tbut wanted\n%v\n\tin test %q", got, want, table.description) + } + }() + } +} + +func TestEndpointGetConsistencyProof(t *testing.T) { + for _, table := range []struct { + description string + breq *bytes.Buffer + trsp *trillian.GetConsistencyProofResponse + terr error + wantCode int + wantItem *types.StItem + }{ + { + description: "invalid: bad request: empty", + breq: bytes.NewBuffer(nil), + wantCode: http.StatusBadRequest, + }, + { + description: "invalid: bad Trillian response: error", + breq: bytes.NewBuffer(marshal(t, types.GetConsistencyProofV1{First: 1, Second: 2})), + terr: fmt.Errorf("backend failure"), + wantCode: http.StatusInternalServerError, + }, + { + description: "valid", + breq: bytes.NewBuffer(marshal(t, types.GetConsistencyProofV1{First: 1, Second: 2})), + trsp: testdata.DefaultTGcpr(t), + wantCode: http.StatusOK, + wantItem: testdata.DefaultConsistencyProof(t, 1, 2), + }, + } { + func() { // run deferred functions at the end of each iteration + ti := newTestInstance(t, nil) + defer ti.ctrl.Finish() + + url := EndpointGetConsistencyProof.Path("http://example.com", ti.instance.LogParameters.Prefix) + req, err := http.NewRequest("POST", url, table.breq) + if err != nil { + t.Fatalf("must create http request: %v", err) + } + req.Header.Set("Content-Type", "application/octet-stream") + if table.trsp != nil || table.terr != nil { + ti.client.EXPECT().GetConsistencyProof(newDeadlineMatcher(), gomock.Any()).Return(table.trsp, table.terr) + } + + w := httptest.NewRecorder() + ti.postHandler(t, EndpointGetConsistencyProof).ServeHTTP(w, req) + if got, want := w.Code, table.wantCode; got != want { + t.Errorf("got error code %d but wanted %d in test %q", got, want, table.description) + } + if w.Code != http.StatusOK { + return + } + + var item types.StItem + if err := types.Unmarshal([]byte(w.Body.String()), &item); err != nil { + t.Errorf("valid response cannot be unmarshalled in test %q: %v", table.description, err) + } + if got, want := item, *table.wantItem; !reflect.DeepEqual(got, want) { + t.Errorf("got item\n%v\n\tbut wanted\n%v\n\tin test %q", got, want, table.description) + } + }() + } +} + +func TestEndpointGetEntriesV1(t *testing.T) { + for _, table := range []struct { + description string + breq *bytes.Buffer + trsp *trillian.GetLeavesByRangeResponse + terr error + wantCode int + wantItem *types.StItemList + }{ + { + description: "invalid: bad request: empty", + breq: bytes.NewBuffer(nil), + wantCode: http.StatusBadRequest, + }, + { + description: "invalid: bad Trillian response: error", + breq: bytes.NewBuffer(marshal(t, types.GetEntriesV1{Start: 0, End: 0})), + terr: fmt.Errorf("backend failure"), + wantCode: http.StatusInternalServerError, + }, + { + description: "valid", // remember that newLogParameters() have testdata.MaxRange configured + breq: bytes.NewBuffer(marshal(t, types.GetEntriesV1{Start: 0, End: uint64(testdata.MaxRange - 1)})), + trsp: testdata.DefaultTGlbrr(t, 0, testdata.MaxRange-1), + wantCode: http.StatusOK, + wantItem: testdata.DefaultStItemList(t, 0, uint64(testdata.MaxRange)-1), + }, + } { + func() { // run deferred functions at the end of each iteration + ti := newTestInstance(t, nil) + defer ti.ctrl.Finish() + + url := EndpointGetEntries.Path("http://example.com", ti.instance.LogParameters.Prefix) + req, err := http.NewRequest("POST", url, table.breq) + if err != nil { + t.Fatalf("must create http request: %v", err) + } + req.Header.Set("Content-Type", "application/octet-stream") + if table.trsp != nil || table.terr != nil { + ti.client.EXPECT().GetLeavesByRange(newDeadlineMatcher(), gomock.Any()).Return(table.trsp, table.terr) + } + + w := httptest.NewRecorder() + ti.postHandler(t, EndpointGetEntries).ServeHTTP(w, req) + if got, want := w.Code, table.wantCode; got != want { + t.Errorf("got error code %d but wanted %d in test %q", got, want, table.description) + } + if w.Code != http.StatusOK { + return + } + + var item types.StItemList + if err := types.Unmarshal([]byte(w.Body.String()), &item); err != nil { + t.Errorf("valid response cannot be unmarshalled in test %q: %v", table.description, err) + } + if got, want := item, *table.wantItem; !reflect.DeepEqual(got, want) { + t.Errorf("got item\n%v\n\tbut wanted\n%v\n\tin test %q", got, want, table.description) + } + }() + } +} + +// TODO: TestWriteOctetResponse +func TestWriteOctetResponse(t *testing.T) { +} + +// deadlineMatcher implements gomock.Matcher, such that an error is raised if +// there is no context.Context deadline set +type deadlineMatcher struct{} + +// newDeadlineMatcher returns a new DeadlineMatcher +func newDeadlineMatcher() gomock.Matcher { + return &deadlineMatcher{} +} + +// Matches returns true if the passed interface is a context with a deadline +func (dm *deadlineMatcher) Matches(i interface{}) bool { + ctx, ok := i.(context.Context) + if !ok { + return false + } + _, ok = ctx.Deadline() + return ok +} + +// String is needed to implement gomock.Matcher +func (dm *deadlineMatcher) String() string { + return fmt.Sprintf("deadlineMatcher{}") +} diff --git a/pkg/instance/instance.go b/pkg/instance/instance.go new file mode 100644 index 0000000..3441a0a --- /dev/null +++ b/pkg/instance/instance.go @@ -0,0 +1,90 @@ +package stfe + +import ( + "context" + "crypto" + "fmt" + "net/http" + "time" + + "github.com/golang/glog" + "github.com/system-transparency/stfe/pkg/state" + "github.com/system-transparency/stfe/pkg/trillian" + "github.com/system-transparency/stfe/pkg/types" +) + +// Config is a collection of log parameters +type Config struct { + LogID string // H(public key), then hex-encoded + TreeID int64 // Merkle tree identifier used by Trillian + Prefix string // The portion between base URL and st/v0 (may be "") + MaxRange int64 // Maximum number of leaves per get-leaves request + Deadline time.Duration // Deadline used for gRPC requests + Interval time.Duration // Cosigning frequency + + // Witnesses map trusted witness identifiers to public verification keys + Witnesses map[[types.HashSize]byte][types.VerificationKeySize]byte +} + +// Instance is an instance of the log's front-end +type Instance struct { + Config // configuration parameters + Client trillian.Client // provides access to the Trillian backend + Signer crypto.Signer // provides access to Ed25519 private key + Stateman state.StateManager // coordinates access to (co)signed tree heads +} + +// Handler implements the http.Handler interface, and contains a reference +// to an STFE server instance as well as a function that uses it. +type Handler struct { + Instance *Instance + Endpoint types.Endpoint + Method string + Handler func(context.Context, *Instance, http.ResponseWriter, *http.Request) (int, error) +} + +// Handlers returns a list of STFE handlers +func (i *Instance) Handlers() []Handler { + return []Handler{ + Handler{Instance: i, Handler: addLeaf, Endpoint: types.EndpointAddLeaf, Method: http.MethodPost}, + Handler{Instance: i, Handler: addCosignature, Endpoint: types.EndpointAddCosignature, Method: http.MethodPost}, + Handler{Instance: i, Handler: getTreeHeadLatest, Endpoint: types.EndpointGetTreeHeadLatest, Method: http.MethodGet}, + Handler{Instance: i, Handler: getTreeHeadToSign, Endpoint: types.EndpointGetTreeHeadToSign, Method: http.MethodGet}, + Handler{Instance: i, Handler: getTreeHeadCosigned, Endpoint: types.EndpointGetTreeHeadCosigned, Method: http.MethodGet}, + Handler{Instance: i, Handler: getConsistencyProof, Endpoint: types.EndpointGetConsistencyProof, Method: http.MethodPost}, + Handler{Instance: i, Handler: getInclusionProof, Endpoint: types.EndpointGetProofByHash, Method: http.MethodPost}, + Handler{Instance: i, Handler: getLeaves, Endpoint: types.EndpointGetLeaves, Method: http.MethodPost}, + } +} + +// Path returns a path that should be configured for this handler +func (h Handler) Path() string { + return h.Endpoint.Path(h.Instance.Prefix, "st", "v0") +} + +// ServeHTTP is part of the http.Handler interface +func (a Handler) ServeHTTP(w http.ResponseWriter, r *http.Request) { + // export prometheus metrics + var now time.Time = time.Now() + var statusCode int + defer func() { + rspcnt.Inc(a.Instance.LogID, string(a.Endpoint), fmt.Sprintf("%d", statusCode)) + latency.Observe(time.Now().Sub(now).Seconds(), a.Instance.LogID, string(a.Endpoint), fmt.Sprintf("%d", statusCode)) + }() + reqcnt.Inc(a.Instance.LogID, string(a.Endpoint)) + + ctx, cancel := context.WithDeadline(r.Context(), now.Add(a.Instance.Deadline)) + defer cancel() + + if r.Method != a.Method { + glog.Warningf("%s/%s: got HTTP %s, wanted HTTP %s", a.Instance.Prefix, string(a.Endpoint), r.Method, a.Method) + http.Error(w, "", http.StatusMethodNotAllowed) + return + } + + statusCode, err := a.Handler(ctx, a.Instance, w, r) + if err != nil { + glog.Warningf("handler error %s/%s: %v", a.Instance.Prefix, a.Endpoint, err) + http.Error(w, fmt.Sprintf("%s%s%s%s", "Error", types.Delim, err.Error(), types.EOL), statusCode) + } +} diff --git a/pkg/instance/instance_test.go b/pkg/instance/instance_test.go new file mode 100644 index 0000000..a7a3d8a --- /dev/null +++ b/pkg/instance/instance_test.go @@ -0,0 +1,158 @@ +package stfe + +import ( + "crypto" + "net/http" + "net/http/httptest" + "testing" + + "github.com/golang/mock/gomock" + "github.com/google/certificate-transparency-go/trillian/mockclient" + "github.com/system-transparency/stfe/pkg/testdata" + "github.com/system-transparency/stfe/pkg/types" +) + +type testInstance struct { + ctrl *gomock.Controller + client *mockclient.MockTrillianLogClient + instance *Instance +} + +// newTestInstances sets up a test instance that uses default log parameters +// with an optional signer, see newLogParameters() for further details. The +// SthSource is instantiated with an ActiveSthSource that has (i) the default +// STH as the currently cosigned STH based on testdata.Ed25519VkWitness, and +// (ii) the default STH without any cosignatures as the currently stable STH. +func newTestInstance(t *testing.T, signer crypto.Signer) *testInstance { + t.Helper() + ctrl := gomock.NewController(t) + client := mockclient.NewMockTrillianLogClient(ctrl) + return &testInstance{ + ctrl: ctrl, + client: client, + instance: &Instance{ + Client: client, + LogParameters: newLogParameters(t, signer), + SthSource: &ActiveSthSource{ + client: client, + logParameters: newLogParameters(t, signer), + currCosth: testdata.DefaultCosth(t, testdata.Ed25519VkLog, [][32]byte{testdata.Ed25519VkWitness}), + nextCosth: testdata.DefaultCosth(t, testdata.Ed25519VkLog, nil), + cosignatureFrom: make(map[[types.NamespaceFingerprintSize]byte]bool), + }, + }, + } +} + +// getHandlers returns all endpoints that use HTTP GET as a map to handlers +func (ti *testInstance) getHandlers(t *testing.T) map[Endpoint]Handler { + t.Helper() + return map[Endpoint]Handler{ + EndpointGetLatestSth: Handler{Instance: ti.instance, Handler: getLatestSth, Endpoint: EndpointGetLatestSth, Method: http.MethodGet}, + EndpointGetStableSth: Handler{Instance: ti.instance, Handler: getStableSth, Endpoint: EndpointGetStableSth, Method: http.MethodGet}, + EndpointGetCosignedSth: Handler{Instance: ti.instance, Handler: getCosignedSth, Endpoint: EndpointGetCosignedSth, Method: http.MethodGet}, + } +} + +// postHandlers returns all endpoints that use HTTP POST as a map to handlers +func (ti *testInstance) postHandlers(t *testing.T) map[Endpoint]Handler { + t.Helper() + return map[Endpoint]Handler{ + EndpointAddEntry: Handler{Instance: ti.instance, Handler: addEntry, Endpoint: EndpointAddEntry, Method: http.MethodPost}, + EndpointAddCosignature: Handler{Instance: ti.instance, Handler: addCosignature, Endpoint: EndpointAddCosignature, Method: http.MethodPost}, + EndpointGetConsistencyProof: Handler{Instance: ti.instance, Handler: getConsistencyProof, Endpoint: EndpointGetConsistencyProof, Method: http.MethodPost}, + EndpointGetProofByHash: Handler{Instance: ti.instance, Handler: getProofByHash, Endpoint: EndpointGetProofByHash, Method: http.MethodPost}, + EndpointGetEntries: Handler{Instance: ti.instance, Handler: getEntries, Endpoint: EndpointGetEntries, Method: http.MethodPost}, + } +} + +// getHandler must return a particular HTTP GET handler +func (ti *testInstance) getHandler(t *testing.T, endpoint Endpoint) Handler { + t.Helper() + handler, ok := ti.getHandlers(t)[endpoint] + if !ok { + t.Fatalf("must return HTTP GET handler for endpoint: %s", endpoint) + } + return handler +} + +// postHandler must return a particular HTTP POST handler +func (ti *testInstance) postHandler(t *testing.T, endpoint Endpoint) Handler { + t.Helper() + handler, ok := ti.postHandlers(t)[endpoint] + if !ok { + t.Fatalf("must return HTTP POST handler for endpoint: %s", endpoint) + } + return handler +} + +// TestHandlers checks that we configured all endpoints and that there are no +// unexpected ones. +func TestHandlers(t *testing.T) { + endpoints := map[Endpoint]bool{ + EndpointAddEntry: false, + EndpointAddCosignature: false, + EndpointGetLatestSth: false, + EndpointGetStableSth: false, + EndpointGetCosignedSth: false, + EndpointGetConsistencyProof: false, + EndpointGetProofByHash: false, + EndpointGetEntries: false, + } + i := &Instance{nil, newLogParameters(t, nil), nil} + for _, handler := range i.Handlers() { + if _, ok := endpoints[handler.Endpoint]; !ok { + t.Errorf("got unexpected endpoint: %s", handler.Endpoint) + } + endpoints[handler.Endpoint] = true + } + for endpoint, ok := range endpoints { + if !ok { + t.Errorf("endpoint %s is not configured", endpoint) + } + } +} + +// TestGetHandlersRejectPost checks that all get handlers reject post requests +func TestGetHandlersRejectPost(t *testing.T) { + ti := newTestInstance(t, nil) + defer ti.ctrl.Finish() + + for endpoint, handler := range ti.getHandlers(t) { + t.Run(string(endpoint), func(t *testing.T) { + s := httptest.NewServer(handler) + defer s.Close() + + url := endpoint.Path(s.URL, ti.instance.LogParameters.Prefix) + if rsp, err := http.Post(url, "application/json", nil); err != nil { + t.Fatalf("http.Post(%s)=(_,%q), want (_,nil)", url, err) + } else if rsp.StatusCode != http.StatusMethodNotAllowed { + t.Errorf("http.Post(%s)=(%d,nil), want (%d, nil)", url, rsp.StatusCode, http.StatusMethodNotAllowed) + } + }) + } +} + +// TestPostHandlersRejectGet checks that all post handlers reject get requests +func TestPostHandlersRejectGet(t *testing.T) { + ti := newTestInstance(t, nil) + defer ti.ctrl.Finish() + + for endpoint, handler := range ti.postHandlers(t) { + t.Run(string(endpoint), func(t *testing.T) { + s := httptest.NewServer(handler) + defer s.Close() + + url := endpoint.Path(s.URL, ti.instance.LogParameters.Prefix) + if rsp, err := http.Get(url); err != nil { + t.Fatalf("http.Get(%s)=(_,%q), want (_,nil)", url, err) + } else if rsp.StatusCode != http.StatusMethodNotAllowed { + t.Errorf("http.Get(%s)=(%d,nil), want (%d, nil)", url, rsp.StatusCode, http.StatusMethodNotAllowed) + } + }) + } +} + +// TODO: TestHandlerPath +func TestHandlerPath(t *testing.T) { +} diff --git a/pkg/instance/metric.go b/pkg/instance/metric.go new file mode 100644 index 0000000..7e3e8b2 --- /dev/null +++ b/pkg/instance/metric.go @@ -0,0 +1,23 @@ +package stfe + +import ( + "github.com/google/trillian/monitoring" + "github.com/google/trillian/monitoring/prometheus" +) + +var ( + reqcnt monitoring.Counter // number of incoming http requests + rspcnt monitoring.Counter // number of valid http responses + latency monitoring.Histogram // request-response latency + lastSthTimestamp monitoring.Gauge // unix timestamp from the most recent sth + lastSthSize monitoring.Gauge // tree size of most recent sth +) + +func init() { + mf := prometheus.MetricFactory{} + reqcnt = mf.NewCounter("http_req", "number of http requests", "logid", "endpoint") + rspcnt = mf.NewCounter("http_rsp", "number of http requests", "logid", "endpoint", "status") + latency = mf.NewHistogram("http_latency", "http request-response latency", "logid", "endpoint", "status") + lastSthTimestamp = mf.NewGauge("last_sth_timestamp", "unix timestamp while handling the most recent sth", "logid") + lastSthSize = mf.NewGauge("last_sth_size", "most recent sth tree size", "logid") +} diff --git a/pkg/instance/request.go b/pkg/instance/request.go new file mode 100644 index 0000000..7475b26 --- /dev/null +++ b/pkg/instance/request.go @@ -0,0 +1,77 @@ +package stfe + +import ( + "crypto/ed25519" + "fmt" + "net/http" + + "github.com/system-transparency/stfe/pkg/types" +) + +func (i *Instance) leafRequestFromHTTP(r *http.Request) (*types.LeafRequest, error) { + var req types.LeafRequest + if err := req.UnmarshalASCII(r.Body); err != nil { + return nil, fmt.Errorf("UnmarshalASCII: %v", err) + } + + vk := ed25519.PublicKey(req.VerificationKey[:]) + msg := req.Message.Marshal() + sig := req.Signature[:] + if !ed25519.Verify(vk, msg, sig) { + return nil, fmt.Errorf("invalid signature") + } + // TODO: check shard hint + // TODO: check domain hint + return &req, nil +} + +func (i *Instance) cosignatureRequestFromHTTP(r *http.Request) (*types.CosignatureRequest, error) { + var req types.CosignatureRequest + if err := req.UnmarshalASCII(r.Body); err != nil { + return nil, fmt.Errorf("unpackOctetPost: %v", err) + } + if _, ok := i.Witnesses[*req.KeyHash]; !ok { + return nil, fmt.Errorf("Unknown witness: %x", req.KeyHash) + } + return &req, nil +} + +func (i *Instance) consistencyProofRequestFromHTTP(r *http.Request) (*types.ConsistencyProofRequest, error) { + var req types.ConsistencyProofRequest + if err := req.UnmarshalASCII(r.Body); err != nil { + return nil, fmt.Errorf("UnmarshalASCII: %v", err) + } + if req.OldSize < 1 { + return nil, fmt.Errorf("OldSize(%d) must be larger than zero", req.OldSize) + } + if req.NewSize <= req.OldSize { + return nil, fmt.Errorf("NewSize(%d) must be larger than OldSize(%d)", req.NewSize, req.OldSize) + } + return &req, nil +} + +func (i *Instance) inclusionProofRequestFromHTTP(r *http.Request) (*types.InclusionProofRequest, error) { + var req types.InclusionProofRequest + if err := req.UnmarshalASCII(r.Body); err != nil { + return nil, fmt.Errorf("UnmarshalASCII: %v", err) + } + if req.TreeSize < 1 { + return nil, fmt.Errorf("TreeSize(%d) must be larger than zero", req.TreeSize) + } + return &req, nil +} + +func (i *Instance) leavesRequestFromHTTP(r *http.Request) (*types.LeavesRequest, error) { + var req types.LeavesRequest + if err := req.UnmarshalASCII(r.Body); err != nil { + return nil, fmt.Errorf("UnmarshalASCII: %v", err) + } + + if req.StartSize > req.EndSize { + return nil, fmt.Errorf("StartSize(%d) must be less than or equal to EndSize(%d)", req.StartSize, req.EndSize) + } + if req.EndSize-req.StartSize+1 > uint64(i.MaxRange) { + req.EndSize = req.StartSize + uint64(i.MaxRange) - 1 + } + return &req, nil +} diff --git a/pkg/instance/request_test.go b/pkg/instance/request_test.go new file mode 100644 index 0000000..0a5a908 --- /dev/null +++ b/pkg/instance/request_test.go @@ -0,0 +1,318 @@ +package stfe + +import ( + "bytes" + //"fmt" + "reflect" + "testing" + //"testing/iotest" + + "net/http" + + "github.com/system-transparency/stfe/pkg/testdata" + "github.com/system-transparency/stfe/pkg/types" +) + +func TestParseAddEntryV1Request(t *testing.T) { + lp := newLogParameters(t, nil) + for _, table := range []struct { + description string + breq *bytes.Buffer + wantErr bool + }{ + { + description: "invalid: nothing to unpack", + breq: bytes.NewBuffer(nil), + wantErr: true, + }, + { + description: "invalid: not a signed checksum entry", + breq: testdata.AddCosignatureBuffer(t, testdata.DefaultSth(t, testdata.Ed25519VkLog), &testdata.Ed25519SkWitness, &testdata.Ed25519VkWitness), + wantErr: true, + }, + { + description: "invalid: untrusted submitter", // only testdata.Ed25519VkSubmitter is registered by default in newLogParameters() + + breq: testdata.AddSignedChecksumBuffer(t, testdata.Ed25519SkSubmitter2, testdata.Ed25519VkSubmitter2), + wantErr: true, + }, + { + description: "invalid: signature does not cover message", + + breq: testdata.AddSignedChecksumBuffer(t, testdata.Ed25519SkSubmitter2, testdata.Ed25519VkSubmitter), + wantErr: true, + }, + { + description: "valid", + breq: testdata.AddSignedChecksumBuffer(t, testdata.Ed25519SkSubmitter, testdata.Ed25519VkSubmitter), + }, // TODO: add test case that disables submitter policy (i.e., unregistered namespaces are accepted) + } { + url := EndpointAddEntry.Path("http://example.com", lp.Prefix) + req, err := http.NewRequest("POST", url, table.breq) + if err != nil { + t.Fatalf("failed creating http request: %v", err) + } + req.Header.Set("Content-Type", "application/octet-stream") + + _, err = lp.parseAddEntryV1Request(req) + if got, want := err != nil, table.wantErr; got != want { + t.Errorf("got errror %v but wanted %v in test %q: %v", got, want, table.description, err) + } + } +} + +func TestParseAddCosignatureV1Request(t *testing.T) { + lp := newLogParameters(t, nil) + for _, table := range []struct { + description string + breq *bytes.Buffer + wantErr bool + }{ + { + description: "invalid: nothing to unpack", + breq: bytes.NewBuffer(nil), + wantErr: true, + }, + { + description: "invalid: not a cosigned sth", + breq: testdata.AddSignedChecksumBuffer(t, testdata.Ed25519SkSubmitter, testdata.Ed25519VkSubmitter), + wantErr: true, + }, + { + description: "invalid: no cosignature", + breq: testdata.AddCosignatureBuffer(t, testdata.DefaultSth(t, testdata.Ed25519VkLog), &testdata.Ed25519SkWitness, nil), + wantErr: true, + }, + { + description: "invalid: untrusted witness", // only testdata.Ed25519VkWitness is registered by default in newLogParameters() + breq: testdata.AddCosignatureBuffer(t, testdata.DefaultSth(t, testdata.Ed25519VkLog), &testdata.Ed25519SkWitness2, &testdata.Ed25519VkWitness2), + wantErr: true, + }, + { + description: "invalid: signature does not cover message", + breq: testdata.AddCosignatureBuffer(t, testdata.DefaultSth(t, testdata.Ed25519VkLog), &testdata.Ed25519SkWitness2, &testdata.Ed25519VkWitness), + wantErr: true, + }, + { + description: "valid", + breq: testdata.AddCosignatureBuffer(t, testdata.DefaultSth(t, testdata.Ed25519VkLog), &testdata.Ed25519SkWitness, &testdata.Ed25519VkWitness), + }, // TODO: add test case that disables witness policy (i.e., unregistered namespaces are accepted) + } { + url := EndpointAddCosignature.Path("http://example.com", lp.Prefix) + req, err := http.NewRequest("POST", url, table.breq) + if err != nil { + t.Fatalf("failed creating http request: %v", err) + } + req.Header.Set("Content-Type", "application/octet-stream") + + _, err = lp.parseAddCosignatureV1Request(req) + if got, want := err != nil, table.wantErr; got != want { + t.Errorf("got errror %v but wanted %v in test %q: %v", got, want, table.description, err) + } + } +} + +func TestNewGetConsistencyProofRequest(t *testing.T) { + lp := newLogParameters(t, nil) + for _, table := range []struct { + description string + req *types.GetConsistencyProofV1 + wantErr bool + }{ + { + description: "invalid: nothing to unpack", + req: nil, + wantErr: true, + }, + { + description: "invalid: first must be larger than zero", + req: &types.GetConsistencyProofV1{First: 0, Second: 0}, + wantErr: true, + }, + { + description: "invalid: second must be larger than first", + req: &types.GetConsistencyProofV1{First: 2, Second: 1}, + wantErr: true, + }, + { + description: "valid", + req: &types.GetConsistencyProofV1{First: 1, Second: 2}, + }, + } { + var buf *bytes.Buffer + if table.req == nil { + buf = bytes.NewBuffer(nil) + } else { + buf = bytes.NewBuffer(marshal(t, *table.req)) + } + + url := EndpointGetConsistencyProof.Path("http://example.com", lp.Prefix) + req, err := http.NewRequest("POST", url, buf) + if err != nil { + t.Fatalf("failed creating http request: %v", err) + } + req.Header.Set("Content-Type", "application/octet-stream") + + _, err = lp.parseGetConsistencyProofV1Request(req) + if got, want := err != nil, table.wantErr; got != want { + t.Errorf("got errror %v but wanted %v in test %q: %v", got, want, table.description, err) + } + } +} + +func TestNewGetProofByHashRequest(t *testing.T) { + lp := newLogParameters(t, nil) + for _, table := range []struct { + description string + req *types.GetProofByHashV1 + wantErr bool + }{ + { + description: "invalid: nothing to unpack", + req: nil, + wantErr: true, + }, + { + description: "invalid: no entry in an empty tree", + req: &types.GetProofByHashV1{TreeSize: 0, Hash: testdata.LeafHash}, + wantErr: true, + }, + { + description: "valid", + req: &types.GetProofByHashV1{TreeSize: 1, Hash: testdata.LeafHash}, + }, + } { + var buf *bytes.Buffer + if table.req == nil { + buf = bytes.NewBuffer(nil) + } else { + buf = bytes.NewBuffer(marshal(t, *table.req)) + } + + url := EndpointGetProofByHash.Path("http://example.com", lp.Prefix) + req, err := http.NewRequest("POST", url, buf) + if err != nil { + t.Fatalf("failed creating http request: %v", err) + } + req.Header.Set("Content-Type", "application/octet-stream") + + _, err = lp.parseGetProofByHashV1Request(req) + if got, want := err != nil, table.wantErr; got != want { + t.Errorf("got errror %v but wanted %v in test %q: %v", got, want, table.description, err) + } + } +} + +func TestParseGetEntriesV1Request(t *testing.T) { + lp := newLogParameters(t, nil) + for _, table := range []struct { + description string + req *types.GetEntriesV1 + wantErr bool + wantReq *types.GetEntriesV1 + }{ + { + description: "invalid: nothing to unpack", + req: nil, + wantErr: true, + }, + { + description: "invalid: start must be larger than end", + req: &types.GetEntriesV1{Start: 1, End: 0}, + wantErr: true, + }, + { + description: "valid: want truncated range", + req: &types.GetEntriesV1{Start: 0, End: uint64(testdata.MaxRange)}, + wantReq: &types.GetEntriesV1{Start: 0, End: uint64(testdata.MaxRange) - 1}, + }, + { + description: "valid", + req: &types.GetEntriesV1{Start: 0, End: 0}, + wantReq: &types.GetEntriesV1{Start: 0, End: 0}, + }, + } { + var buf *bytes.Buffer + if table.req == nil { + buf = bytes.NewBuffer(nil) + } else { + buf = bytes.NewBuffer(marshal(t, *table.req)) + } + + url := EndpointGetEntries.Path("http://example.com", lp.Prefix) + req, err := http.NewRequest("POST", url, buf) + if err != nil { + t.Fatalf("failed creating http request: %v", err) + } + req.Header.Set("Content-Type", "application/octet-stream") + + output, err := lp.parseGetEntriesV1Request(req) + if got, want := err != nil, table.wantErr; got != want { + t.Errorf("got errror %v but wanted %v in test %q: %v", got, want, table.description, err) + } + if err != nil { + continue + } + if got, want := output, table.wantReq; !reflect.DeepEqual(got, want) { + t.Errorf("got request\n%v\n\tbut wanted\n%v\n\t in test %q", got, want, table.description) + } + } +} + +func TestUnpackOctetPost(t *testing.T) { + for _, table := range []struct { + description string + req *http.Request + out interface{} + wantErr bool + }{ + //{ + // description: "invalid: cannot read request body", + // req: func() *http.Request { + // req, err := http.NewRequest(http.MethodPost, "", iotest.ErrReader(fmt.Errorf("bad reader"))) + // if err != nil { + // t.Fatalf("must make new http request: %v", err) + // } + // return req + // }(), + // out: &types.StItem{}, + // wantErr: true, + //}, // testcase requires Go 1.16 + { + description: "invalid: cannot unmarshal", + req: func() *http.Request { + req, err := http.NewRequest(http.MethodPost, "", bytes.NewBuffer(nil)) + if err != nil { + t.Fatalf("must make new http request: %v", err) + } + return req + }(), + out: &types.StItem{}, + wantErr: true, + }, + { + description: "valid", + req: func() *http.Request { + req, err := http.NewRequest(http.MethodPost, "", bytes.NewBuffer([]byte{0})) + if err != nil { + t.Fatalf("must make new http request: %v", err) + } + return req + }(), + out: &struct{ SomeUint8 uint8 }{}, + }, + } { + err := unpackOctetPost(table.req, table.out) + if got, want := err != nil, table.wantErr; got != want { + t.Errorf("got error %v but wanted %v in test %q", got, want, table.description) + } + } +} + +func marshal(t *testing.T, out interface{}) []byte { + b, err := types.Marshal(out) + if err != nil { + t.Fatalf("must marshal: %v", err) + } + return b +} diff --git a/pkg/mocks/crypto.go b/pkg/mocks/crypto.go new file mode 100644 index 0000000..87c883a --- /dev/null +++ b/pkg/mocks/crypto.go @@ -0,0 +1,23 @@ +package mocks + +import ( + "crypto" + "crypto/ed25519" + "io" +) + +// TestSign implements the signer interface. It can be used to mock an Ed25519 +// signer that always return the same public key, signature, and error. +type TestSigner struct { + PublicKey *[ed25519.PublicKeySize]byte + Signature *[ed25519.SignatureSize]byte + Error error +} + +func (ts *TestSigner) Public() crypto.PublicKey { + return ed25519.PublicKey(ts.PublicKey[:]) +} + +func (ts *TestSigner) Sign(rand io.Reader, digest []byte, opts crypto.SignerOpts) ([]byte, error) { + return ts.Signature[:], ts.Error +} diff --git a/pkg/mocks/stfe.go b/pkg/mocks/stfe.go new file mode 100644 index 0000000..def5bc6 --- /dev/null +++ b/pkg/mocks/stfe.go @@ -0,0 +1,110 @@ +// Code generated by MockGen. DO NOT EDIT. +// Source: github.com/system-transparency/stfe/trillian (interfaces: Client) + +// Package mocks is a generated GoMock package. +package mocks + +import ( + context "context" + reflect "reflect" + + gomock "github.com/golang/mock/gomock" + types "github.com/system-transparency/stfe/pkg/types" +) + +// MockClient is a mock of Client interface. +type MockClient struct { + ctrl *gomock.Controller + recorder *MockClientMockRecorder +} + +// MockClientMockRecorder is the mock recorder for MockClient. +type MockClientMockRecorder struct { + mock *MockClient +} + +// NewMockClient creates a new mock instance. +func NewMockClient(ctrl *gomock.Controller) *MockClient { + mock := &MockClient{ctrl: ctrl} + mock.recorder = &MockClientMockRecorder{mock} + return mock +} + +// EXPECT returns an object that allows the caller to indicate expected use. +func (m *MockClient) EXPECT() *MockClientMockRecorder { + return m.recorder +} + +// AddLeaf mocks base method. +func (m *MockClient) AddLeaf(arg0 context.Context, arg1 *types.LeafRequest) error { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "AddLeaf", arg0, arg1) + ret0, _ := ret[0].(error) + return ret0 +} + +// AddLeaf indicates an expected call of AddLeaf. +func (mr *MockClientMockRecorder) AddLeaf(arg0, arg1 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "AddLeaf", reflect.TypeOf((*MockClient)(nil).AddLeaf), arg0, arg1) +} + +// GetConsistencyProof mocks base method. +func (m *MockClient) GetConsistencyProof(arg0 context.Context, arg1 *types.ConsistencyProofRequest) (*types.ConsistencyProof, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "GetConsistencyProof", arg0, arg1) + ret0, _ := ret[0].(*types.ConsistencyProof) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// GetConsistencyProof indicates an expected call of GetConsistencyProof. +func (mr *MockClientMockRecorder) GetConsistencyProof(arg0, arg1 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetConsistencyProof", reflect.TypeOf((*MockClient)(nil).GetConsistencyProof), arg0, arg1) +} + +// GetInclusionProof mocks base method. +func (m *MockClient) GetInclusionProof(arg0 context.Context, arg1 *types.InclusionProofRequest) (*types.InclusionProof, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "GetInclusionProof", arg0, arg1) + ret0, _ := ret[0].(*types.InclusionProof) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// GetInclusionProof indicates an expected call of GetInclusionProof. +func (mr *MockClientMockRecorder) GetInclusionProof(arg0, arg1 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetInclusionProof", reflect.TypeOf((*MockClient)(nil).GetInclusionProof), arg0, arg1) +} + +// GetLeaves mocks base method. +func (m *MockClient) GetLeaves(arg0 context.Context, arg1 *types.LeavesRequest) (*types.LeafList, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "GetLeaves", arg0, arg1) + ret0, _ := ret[0].(*types.LeafList) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// GetLeaves indicates an expected call of GetLeaves. +func (mr *MockClientMockRecorder) GetLeaves(arg0, arg1 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetLeaves", reflect.TypeOf((*MockClient)(nil).GetLeaves), arg0, arg1) +} + +// GetTreeHead mocks base method. +func (m *MockClient) GetTreeHead(arg0 context.Context) (*types.TreeHead, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "GetTreeHead", arg0) + ret0, _ := ret[0].(*types.TreeHead) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// GetTreeHead indicates an expected call of GetTreeHead. +func (mr *MockClientMockRecorder) GetTreeHead(arg0 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetTreeHead", reflect.TypeOf((*MockClient)(nil).GetTreeHead), arg0) +} diff --git a/pkg/mocks/trillian.go b/pkg/mocks/trillian.go new file mode 100644 index 0000000..8aa3a58 --- /dev/null +++ b/pkg/mocks/trillian.go @@ -0,0 +1,317 @@ +// Code generated by MockGen. DO NOT EDIT. +// Source: github.com/google/trillian (interfaces: TrillianLogClient) + +// Package mocks is a generated GoMock package. +package mocks + +import ( + context "context" + reflect "reflect" + + gomock "github.com/golang/mock/gomock" + trillian "github.com/google/trillian" + grpc "google.golang.org/grpc" +) + +// MockTrillianLogClient is a mock of TrillianLogClient interface. +type MockTrillianLogClient struct { + ctrl *gomock.Controller + recorder *MockTrillianLogClientMockRecorder +} + +// MockTrillianLogClientMockRecorder is the mock recorder for MockTrillianLogClient. +type MockTrillianLogClientMockRecorder struct { + mock *MockTrillianLogClient +} + +// NewMockTrillianLogClient creates a new mock instance. +func NewMockTrillianLogClient(ctrl *gomock.Controller) *MockTrillianLogClient { + mock := &MockTrillianLogClient{ctrl: ctrl} + mock.recorder = &MockTrillianLogClientMockRecorder{mock} + return mock +} + +// EXPECT returns an object that allows the caller to indicate expected use. +func (m *MockTrillianLogClient) EXPECT() *MockTrillianLogClientMockRecorder { + return m.recorder +} + +// AddSequencedLeaf mocks base method. +func (m *MockTrillianLogClient) AddSequencedLeaf(arg0 context.Context, arg1 *trillian.AddSequencedLeafRequest, arg2 ...grpc.CallOption) (*trillian.AddSequencedLeafResponse, error) { + m.ctrl.T.Helper() + varargs := []interface{}{arg0, arg1} + for _, a := range arg2 { + varargs = append(varargs, a) + } + ret := m.ctrl.Call(m, "AddSequencedLeaf", varargs...) + ret0, _ := ret[0].(*trillian.AddSequencedLeafResponse) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// AddSequencedLeaf indicates an expected call of AddSequencedLeaf. +func (mr *MockTrillianLogClientMockRecorder) AddSequencedLeaf(arg0, arg1 interface{}, arg2 ...interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + varargs := append([]interface{}{arg0, arg1}, arg2...) + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "AddSequencedLeaf", reflect.TypeOf((*MockTrillianLogClient)(nil).AddSequencedLeaf), varargs...) +} + +// AddSequencedLeaves mocks base method. +func (m *MockTrillianLogClient) AddSequencedLeaves(arg0 context.Context, arg1 *trillian.AddSequencedLeavesRequest, arg2 ...grpc.CallOption) (*trillian.AddSequencedLeavesResponse, error) { + m.ctrl.T.Helper() + varargs := []interface{}{arg0, arg1} + for _, a := range arg2 { + varargs = append(varargs, a) + } + ret := m.ctrl.Call(m, "AddSequencedLeaves", varargs...) + ret0, _ := ret[0].(*trillian.AddSequencedLeavesResponse) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// AddSequencedLeaves indicates an expected call of AddSequencedLeaves. +func (mr *MockTrillianLogClientMockRecorder) AddSequencedLeaves(arg0, arg1 interface{}, arg2 ...interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + varargs := append([]interface{}{arg0, arg1}, arg2...) + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "AddSequencedLeaves", reflect.TypeOf((*MockTrillianLogClient)(nil).AddSequencedLeaves), varargs...) +} + +// GetConsistencyProof mocks base method. +func (m *MockTrillianLogClient) GetConsistencyProof(arg0 context.Context, arg1 *trillian.GetConsistencyProofRequest, arg2 ...grpc.CallOption) (*trillian.GetConsistencyProofResponse, error) { + m.ctrl.T.Helper() + varargs := []interface{}{arg0, arg1} + for _, a := range arg2 { + varargs = append(varargs, a) + } + ret := m.ctrl.Call(m, "GetConsistencyProof", varargs...) + ret0, _ := ret[0].(*trillian.GetConsistencyProofResponse) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// GetConsistencyProof indicates an expected call of GetConsistencyProof. +func (mr *MockTrillianLogClientMockRecorder) GetConsistencyProof(arg0, arg1 interface{}, arg2 ...interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + varargs := append([]interface{}{arg0, arg1}, arg2...) + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetConsistencyProof", reflect.TypeOf((*MockTrillianLogClient)(nil).GetConsistencyProof), varargs...) +} + +// GetEntryAndProof mocks base method. +func (m *MockTrillianLogClient) GetEntryAndProof(arg0 context.Context, arg1 *trillian.GetEntryAndProofRequest, arg2 ...grpc.CallOption) (*trillian.GetEntryAndProofResponse, error) { + m.ctrl.T.Helper() + varargs := []interface{}{arg0, arg1} + for _, a := range arg2 { + varargs = append(varargs, a) + } + ret := m.ctrl.Call(m, "GetEntryAndProof", varargs...) + ret0, _ := ret[0].(*trillian.GetEntryAndProofResponse) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// GetEntryAndProof indicates an expected call of GetEntryAndProof. +func (mr *MockTrillianLogClientMockRecorder) GetEntryAndProof(arg0, arg1 interface{}, arg2 ...interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + varargs := append([]interface{}{arg0, arg1}, arg2...) + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetEntryAndProof", reflect.TypeOf((*MockTrillianLogClient)(nil).GetEntryAndProof), varargs...) +} + +// GetInclusionProof mocks base method. +func (m *MockTrillianLogClient) GetInclusionProof(arg0 context.Context, arg1 *trillian.GetInclusionProofRequest, arg2 ...grpc.CallOption) (*trillian.GetInclusionProofResponse, error) { + m.ctrl.T.Helper() + varargs := []interface{}{arg0, arg1} + for _, a := range arg2 { + varargs = append(varargs, a) + } + ret := m.ctrl.Call(m, "GetInclusionProof", varargs...) + ret0, _ := ret[0].(*trillian.GetInclusionProofResponse) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// GetInclusionProof indicates an expected call of GetInclusionProof. +func (mr *MockTrillianLogClientMockRecorder) GetInclusionProof(arg0, arg1 interface{}, arg2 ...interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + varargs := append([]interface{}{arg0, arg1}, arg2...) + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetInclusionProof", reflect.TypeOf((*MockTrillianLogClient)(nil).GetInclusionProof), varargs...) +} + +// GetInclusionProofByHash mocks base method. +func (m *MockTrillianLogClient) GetInclusionProofByHash(arg0 context.Context, arg1 *trillian.GetInclusionProofByHashRequest, arg2 ...grpc.CallOption) (*trillian.GetInclusionProofByHashResponse, error) { + m.ctrl.T.Helper() + varargs := []interface{}{arg0, arg1} + for _, a := range arg2 { + varargs = append(varargs, a) + } + ret := m.ctrl.Call(m, "GetInclusionProofByHash", varargs...) + ret0, _ := ret[0].(*trillian.GetInclusionProofByHashResponse) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// GetInclusionProofByHash indicates an expected call of GetInclusionProofByHash. +func (mr *MockTrillianLogClientMockRecorder) GetInclusionProofByHash(arg0, arg1 interface{}, arg2 ...interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + varargs := append([]interface{}{arg0, arg1}, arg2...) + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetInclusionProofByHash", reflect.TypeOf((*MockTrillianLogClient)(nil).GetInclusionProofByHash), varargs...) +} + +// GetLatestSignedLogRoot mocks base method. +func (m *MockTrillianLogClient) GetLatestSignedLogRoot(arg0 context.Context, arg1 *trillian.GetLatestSignedLogRootRequest, arg2 ...grpc.CallOption) (*trillian.GetLatestSignedLogRootResponse, error) { + m.ctrl.T.Helper() + varargs := []interface{}{arg0, arg1} + for _, a := range arg2 { + varargs = append(varargs, a) + } + ret := m.ctrl.Call(m, "GetLatestSignedLogRoot", varargs...) + ret0, _ := ret[0].(*trillian.GetLatestSignedLogRootResponse) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// GetLatestSignedLogRoot indicates an expected call of GetLatestSignedLogRoot. +func (mr *MockTrillianLogClientMockRecorder) GetLatestSignedLogRoot(arg0, arg1 interface{}, arg2 ...interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + varargs := append([]interface{}{arg0, arg1}, arg2...) + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetLatestSignedLogRoot", reflect.TypeOf((*MockTrillianLogClient)(nil).GetLatestSignedLogRoot), varargs...) +} + +// GetLeavesByHash mocks base method. +func (m *MockTrillianLogClient) GetLeavesByHash(arg0 context.Context, arg1 *trillian.GetLeavesByHashRequest, arg2 ...grpc.CallOption) (*trillian.GetLeavesByHashResponse, error) { + m.ctrl.T.Helper() + varargs := []interface{}{arg0, arg1} + for _, a := range arg2 { + varargs = append(varargs, a) + } + ret := m.ctrl.Call(m, "GetLeavesByHash", varargs...) + ret0, _ := ret[0].(*trillian.GetLeavesByHashResponse) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// GetLeavesByHash indicates an expected call of GetLeavesByHash. +func (mr *MockTrillianLogClientMockRecorder) GetLeavesByHash(arg0, arg1 interface{}, arg2 ...interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + varargs := append([]interface{}{arg0, arg1}, arg2...) + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetLeavesByHash", reflect.TypeOf((*MockTrillianLogClient)(nil).GetLeavesByHash), varargs...) +} + +// GetLeavesByIndex mocks base method. +func (m *MockTrillianLogClient) GetLeavesByIndex(arg0 context.Context, arg1 *trillian.GetLeavesByIndexRequest, arg2 ...grpc.CallOption) (*trillian.GetLeavesByIndexResponse, error) { + m.ctrl.T.Helper() + varargs := []interface{}{arg0, arg1} + for _, a := range arg2 { + varargs = append(varargs, a) + } + ret := m.ctrl.Call(m, "GetLeavesByIndex", varargs...) + ret0, _ := ret[0].(*trillian.GetLeavesByIndexResponse) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// GetLeavesByIndex indicates an expected call of GetLeavesByIndex. +func (mr *MockTrillianLogClientMockRecorder) GetLeavesByIndex(arg0, arg1 interface{}, arg2 ...interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + varargs := append([]interface{}{arg0, arg1}, arg2...) + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetLeavesByIndex", reflect.TypeOf((*MockTrillianLogClient)(nil).GetLeavesByIndex), varargs...) +} + +// GetLeavesByRange mocks base method. +func (m *MockTrillianLogClient) GetLeavesByRange(arg0 context.Context, arg1 *trillian.GetLeavesByRangeRequest, arg2 ...grpc.CallOption) (*trillian.GetLeavesByRangeResponse, error) { + m.ctrl.T.Helper() + varargs := []interface{}{arg0, arg1} + for _, a := range arg2 { + varargs = append(varargs, a) + } + ret := m.ctrl.Call(m, "GetLeavesByRange", varargs...) + ret0, _ := ret[0].(*trillian.GetLeavesByRangeResponse) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// GetLeavesByRange indicates an expected call of GetLeavesByRange. +func (mr *MockTrillianLogClientMockRecorder) GetLeavesByRange(arg0, arg1 interface{}, arg2 ...interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + varargs := append([]interface{}{arg0, arg1}, arg2...) + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetLeavesByRange", reflect.TypeOf((*MockTrillianLogClient)(nil).GetLeavesByRange), varargs...) +} + +// GetSequencedLeafCount mocks base method. +func (m *MockTrillianLogClient) GetSequencedLeafCount(arg0 context.Context, arg1 *trillian.GetSequencedLeafCountRequest, arg2 ...grpc.CallOption) (*trillian.GetSequencedLeafCountResponse, error) { + m.ctrl.T.Helper() + varargs := []interface{}{arg0, arg1} + for _, a := range arg2 { + varargs = append(varargs, a) + } + ret := m.ctrl.Call(m, "GetSequencedLeafCount", varargs...) + ret0, _ := ret[0].(*trillian.GetSequencedLeafCountResponse) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// GetSequencedLeafCount indicates an expected call of GetSequencedLeafCount. +func (mr *MockTrillianLogClientMockRecorder) GetSequencedLeafCount(arg0, arg1 interface{}, arg2 ...interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + varargs := append([]interface{}{arg0, arg1}, arg2...) + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetSequencedLeafCount", reflect.TypeOf((*MockTrillianLogClient)(nil).GetSequencedLeafCount), varargs...) +} + +// InitLog mocks base method. +func (m *MockTrillianLogClient) InitLog(arg0 context.Context, arg1 *trillian.InitLogRequest, arg2 ...grpc.CallOption) (*trillian.InitLogResponse, error) { + m.ctrl.T.Helper() + varargs := []interface{}{arg0, arg1} + for _, a := range arg2 { + varargs = append(varargs, a) + } + ret := m.ctrl.Call(m, "InitLog", varargs...) + ret0, _ := ret[0].(*trillian.InitLogResponse) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// InitLog indicates an expected call of InitLog. +func (mr *MockTrillianLogClientMockRecorder) InitLog(arg0, arg1 interface{}, arg2 ...interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + varargs := append([]interface{}{arg0, arg1}, arg2...) + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "InitLog", reflect.TypeOf((*MockTrillianLogClient)(nil).InitLog), varargs...) +} + +// QueueLeaf mocks base method. +func (m *MockTrillianLogClient) QueueLeaf(arg0 context.Context, arg1 *trillian.QueueLeafRequest, arg2 ...grpc.CallOption) (*trillian.QueueLeafResponse, error) { + m.ctrl.T.Helper() + varargs := []interface{}{arg0, arg1} + for _, a := range arg2 { + varargs = append(varargs, a) + } + ret := m.ctrl.Call(m, "QueueLeaf", varargs...) + ret0, _ := ret[0].(*trillian.QueueLeafResponse) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// QueueLeaf indicates an expected call of QueueLeaf. +func (mr *MockTrillianLogClientMockRecorder) QueueLeaf(arg0, arg1 interface{}, arg2 ...interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + varargs := append([]interface{}{arg0, arg1}, arg2...) + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "QueueLeaf", reflect.TypeOf((*MockTrillianLogClient)(nil).QueueLeaf), varargs...) +} + +// QueueLeaves mocks base method. +func (m *MockTrillianLogClient) QueueLeaves(arg0 context.Context, arg1 *trillian.QueueLeavesRequest, arg2 ...grpc.CallOption) (*trillian.QueueLeavesResponse, error) { + m.ctrl.T.Helper() + varargs := []interface{}{arg0, arg1} + for _, a := range arg2 { + varargs = append(varargs, a) + } + ret := m.ctrl.Call(m, "QueueLeaves", varargs...) + ret0, _ := ret[0].(*trillian.QueueLeavesResponse) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// QueueLeaves indicates an expected call of QueueLeaves. +func (mr *MockTrillianLogClientMockRecorder) QueueLeaves(arg0, arg1 interface{}, arg2 ...interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + varargs := append([]interface{}{arg0, arg1}, arg2...) + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "QueueLeaves", reflect.TypeOf((*MockTrillianLogClient)(nil).QueueLeaves), varargs...) +} diff --git a/pkg/state/state_manager.go b/pkg/state/state_manager.go new file mode 100644 index 0000000..dfa73f4 --- /dev/null +++ b/pkg/state/state_manager.go @@ -0,0 +1,154 @@ +package state + +import ( + "context" + "crypto" + "fmt" + "reflect" + "sync" + "time" + + "github.com/golang/glog" + "github.com/google/certificate-transparency-go/schedule" + "github.com/system-transparency/stfe/pkg/trillian" + "github.com/system-transparency/stfe/pkg/types" +) + +// StateManager coordinates access to the log's tree heads and (co)signatures +type StateManager interface { + Latest(context.Context) (*types.SignedTreeHead, error) + ToSign(context.Context) (*types.SignedTreeHead, error) + Cosigned(context.Context) (*types.SignedTreeHead, error) + AddCosignature(context.Context, *[types.VerificationKeySize]byte, *[types.SignatureSize]byte) error + Run(context.Context) +} + +// StateManagerSingle implements the StateManager interface. It is assumed that +// the log server is running on a single-instance machine. So, no coordination. +type StateManagerSingle struct { + client trillian.Client + signer crypto.Signer + interval time.Duration + deadline time.Duration + sync.RWMutex + + // cosigned is the current cosigned tree head that is being served + cosigned types.SignedTreeHead + + // tosign is the current tree head that is being cosigned by witnesses + tosign types.SignedTreeHead + + // cosignature keeps track of all cosignatures for the tosign tree head + cosignature map[[types.HashSize]byte]*types.SigIdent +} + +func NewStateManagerSingle(client trillian.Client, signer crypto.Signer, interval, deadline time.Duration) (*StateManagerSingle, error) { + sm := &StateManagerSingle{ + client: client, + signer: signer, + interval: interval, + deadline: deadline, + } + + ctx, _ := context.WithTimeout(context.Background(), sm.deadline) + sth, err := sm.Latest(ctx) + if err != nil { + return nil, fmt.Errorf("Latest: %v", err) + } + + sm.cosigned = *sth + sm.tosign = *sth + sm.cosignature = map[[types.HashSize]byte]*types.SigIdent{ + *sth.SigIdent[0].KeyHash: sth.SigIdent[0], // log signature + } + return sm, nil +} + +func (sm *StateManagerSingle) Run(ctx context.Context) { + schedule.Every(ctx, sm.interval, func(ctx context.Context) { + ictx, _ := context.WithTimeout(ctx, sm.deadline) + nextTreeHead, err := sm.Latest(ictx) + if err != nil { + glog.Warningf("rotate failed: Latest: %v", err) + return + } + + sm.Lock() + defer sm.Unlock() + sm.rotate(nextTreeHead) + }) +} + +func (sm *StateManagerSingle) Latest(ctx context.Context) (*types.SignedTreeHead, error) { + th, err := sm.client.GetTreeHead(ctx) + if err != nil { + return nil, fmt.Errorf("LatestTreeHead: %v", err) + } + sth, err := th.Sign(sm.signer) + if err != nil { + return nil, fmt.Errorf("sign: %v", err) + } + return sth, nil +} + +func (sm *StateManagerSingle) ToSign(_ context.Context) (*types.SignedTreeHead, error) { + sm.RLock() + defer sm.RUnlock() + return &sm.tosign, nil +} + +func (sm *StateManagerSingle) Cosigned(_ context.Context) (*types.SignedTreeHead, error) { + sm.RLock() + defer sm.RUnlock() + return &sm.cosigned, nil +} + +func (sm *StateManagerSingle) AddCosignature(_ context.Context, vk *[types.VerificationKeySize]byte, sig *[types.SignatureSize]byte) error { + sm.Lock() + defer sm.Unlock() + + if err := sm.tosign.TreeHead.Verify(vk, sig); err != nil { + return fmt.Errorf("Verify: %v", err) + } + witness := types.Hash(vk[:]) + if _, ok := sm.cosignature[*witness]; ok { + return fmt.Errorf("signature-signer pair is a duplicate") + } + sm.cosignature[*witness] = &types.SigIdent{ + Signature: sig, + KeyHash: witness, + } + + glog.V(3).Infof("accepted new cosignature from witness: %x", *witness) + return nil +} + +// rotate rotates the log's cosigned and stable STH. The caller must aquire the +// source's read-write lock if there are concurrent reads and/or writes. +func (sm *StateManagerSingle) rotate(next *types.SignedTreeHead) { + if reflect.DeepEqual(sm.cosigned.TreeHead, sm.tosign.TreeHead) { + // cosigned and tosign are the same. So, we need to merge all + // cosignatures that we already had with the new collected ones. + for _, sigident := range sm.cosigned.SigIdent { + if _, ok := sm.cosignature[*sigident.KeyHash]; !ok { + sm.cosignature[*sigident.KeyHash] = sigident + } + } + glog.V(3).Infof("cosigned tree head repeated, merged signatures") + } + var cosignatures []*types.SigIdent + for _, sigident := range sm.cosignature { + cosignatures = append(cosignatures, sigident) + } + + // Update cosigned tree head + sm.cosigned.TreeHead = sm.tosign.TreeHead + sm.cosigned.SigIdent = cosignatures + + // Update to-sign tree head + sm.tosign = *next + sm.cosignature = map[[types.HashSize]byte]*types.SigIdent{ + *next.SigIdent[0].KeyHash: next.SigIdent[0], // log signature + } + glog.V(3).Infof("rotated tree heads") +} diff --git a/pkg/state/state_manager_test.go b/pkg/state/state_manager_test.go new file mode 100644 index 0000000..08990cc --- /dev/null +++ b/pkg/state/state_manager_test.go @@ -0,0 +1,393 @@ +package state + +import ( + "bytes" + "context" + "crypto" + "crypto/ed25519" + "crypto/rand" + "fmt" + "reflect" + "testing" + "time" + + "github.com/golang/mock/gomock" + "github.com/system-transparency/stfe/pkg/mocks" + "github.com/system-transparency/stfe/pkg/types" +) + +var ( + testSig = &[types.SignatureSize]byte{} + testPub = &[types.VerificationKeySize]byte{} + testTH = &types.TreeHead{ + Timestamp: 0, + TreeSize: 0, + RootHash: types.Hash(nil), + } + testSigIdent = &types.SigIdent{ + Signature: testSig, + KeyHash: types.Hash(testPub[:]), + } + testSTH = &types.SignedTreeHead{ + TreeHead: *testTH, + SigIdent: []*types.SigIdent{testSigIdent}, + } + testSignerOK = &mocks.TestSigner{testPub, testSig, nil} + testSignerErr = &mocks.TestSigner{testPub, testSig, fmt.Errorf("something went wrong")} +) + +func TestNewStateManagerSingle(t *testing.T) { + for _, table := range []struct { + description string + signer crypto.Signer + rsp *types.TreeHead + err error + wantErr bool + wantSth *types.SignedTreeHead + }{ + { + description: "invalid: backend failure", + signer: testSignerOK, + err: fmt.Errorf("something went wrong"), + wantErr: true, + }, + { + description: "valid", + signer: testSignerOK, + rsp: testTH, + wantSth: testSTH, + }, + } { + // Run deferred functions at the end of each iteration + func() { + ctrl := gomock.NewController(t) + defer ctrl.Finish() + client := mocks.NewMockClient(ctrl) + client.EXPECT().GetTreeHead(gomock.Any()).Return(table.rsp, table.err) + + sm, err := NewStateManagerSingle(client, table.signer, time.Duration(0), time.Duration(0)) + if got, want := err != nil, table.wantErr; got != want { + t.Errorf("got error %v but wanted %v in test %q: %v", got, want, table.description, err) + } + if err != nil { + return + } + if got, want := &sm.cosigned, table.wantSth; !reflect.DeepEqual(got, want) { + t.Errorf("got cosigned tree head\n\t%v\nbut wanted\n\t%v\nin test %q", got, want, table.description) + } + if got, want := &sm.tosign, table.wantSth; !reflect.DeepEqual(got, want) { + t.Errorf("got tosign tree head\n\t%v\nbut wanted\n\t%v\nin test %q", got, want, table.description) + } + // we only have log signature on startup + if got, want := len(sm.cosignature), 1; got != want { + t.Errorf("got %d cosignatures but wanted %d in test %q", got, want, table.description) + } + }() + } +} + +func TestLatest(t *testing.T) { + for _, table := range []struct { + description string + signer crypto.Signer + rsp *types.TreeHead + err error + wantErr bool + wantSth *types.SignedTreeHead + }{ + { + description: "invalid: backend failure", + signer: testSignerOK, + err: fmt.Errorf("something went wrong"), + wantErr: true, + }, + { + description: "invalid: signature failure", + rsp: testTH, + signer: testSignerErr, + wantErr: true, + }, + { + description: "valid", + signer: testSignerOK, + rsp: testTH, + wantSth: testSTH, + }, + } { + // Run deferred functions at the end of each iteration + func() { + ctrl := gomock.NewController(t) + defer ctrl.Finish() + client := mocks.NewMockClient(ctrl) + client.EXPECT().GetTreeHead(gomock.Any()).Return(table.rsp, table.err) + sm := StateManagerSingle{ + client: client, + signer: table.signer, + } + + sth, err := sm.Latest(context.Background()) + if got, want := err != nil, table.wantErr; got != want { + t.Errorf("got error %v but wanted %v in test %q: %v", got, want, table.description, err) + } + if err != nil { + return + } + if got, want := sth, table.wantSth; !reflect.DeepEqual(got, want) { + t.Errorf("got signed tree head\n\t%v\nbut wanted\n\t%v\nin test %q", got, want, table.description) + } + }() + } +} + +func TestToSign(t *testing.T) { + description := "valid" + sm := StateManagerSingle{ + tosign: *testSTH, + } + sth, err := sm.ToSign(context.Background()) + if err != nil { + t.Errorf("ToSign should not fail with error: %v", err) + return + } + if got, want := sth, testSTH; !reflect.DeepEqual(got, want) { + t.Errorf("got signed tree head\n\t%v\nbut wanted\n\t%v\nin test %q", got, want, description) + } +} + +func TestCosigned(t *testing.T) { + description := "valid" + sm := StateManagerSingle{ + cosigned: *testSTH, + } + sth, err := sm.Cosigned(context.Background()) + if err != nil { + t.Errorf("Cosigned should not fail with error: %v", err) + return + } + if got, want := sth, testSTH; !reflect.DeepEqual(got, want) { + t.Errorf("got signed tree head\n\t%v\nbut wanted\n\t%v\nin test %q", got, want, description) + } +} + +func TestAddCosignature(t *testing.T) { + vk, sk, err := ed25519.GenerateKey(rand.Reader) + if err != nil { + t.Fatalf("GenerateKey: %v", err) + } + if bytes.Equal(vk[:], testPub[:]) { + t.Fatalf("Sampled same key as testPub, aborting...") + } + var vkArray [types.VerificationKeySize]byte + copy(vkArray[:], vk[:]) + + for _, table := range []struct { + description string + signer crypto.Signer + vk *[types.VerificationKeySize]byte + th *types.TreeHead + wantErr bool + }{ + { + description: "invalid: signature error", + signer: sk, + vk: testPub, // wrong key for message + th: testTH, + wantErr: true, + }, + { + description: "valid", + signer: sk, + vk: &vkArray, + th: testTH, + }, + } { + sth, _ := table.th.Sign(testSignerOK) + logKeyHash := sth.SigIdent[0].KeyHash + logSigIdent := sth.SigIdent[0] + sm := &StateManagerSingle{ + signer: testSignerOK, + cosigned: *sth, + tosign: *sth, + cosignature: map[[types.HashSize]byte]*types.SigIdent{ + *logKeyHash: logSigIdent, + }, + } + + // Prepare witness signature + sth, err := table.th.Sign(table.signer) + if err != nil { + t.Fatalf("Sign: %v", err) + } + witnessKeyHash := sth.SigIdent[0].KeyHash + witnessSigIdent := sth.SigIdent[0] + + // Add witness signature + err = sm.AddCosignature(context.Background(), table.vk, witnessSigIdent.Signature) + if got, want := err != nil, table.wantErr; got != want { + t.Errorf("got error %v but wanted %v in test %q: %v", got, want, table.description, err) + } + if err != nil { + continue + } + + // We should have two signatures (log + witness) + if got, want := len(sm.cosignature), 2; got != want { + t.Errorf("got %d cosignatures but wanted %v in test %q", got, want, table.description) + continue + } + // check that log signature is there + sigident, ok := sm.cosignature[*logKeyHash] + if !ok { + t.Errorf("log signature is missing") + continue + } + if got, want := sigident, logSigIdent; !reflect.DeepEqual(got, want) { + t.Errorf("got log sigident\n\t%v\nbut wanted\n\t%v\nin test %q", got, want, table.description) + } + // check that witness signature is there + sigident, ok = sm.cosignature[*witnessKeyHash] + if !ok { + t.Errorf("witness signature is missing") + continue + } + if got, want := sigident, witnessSigIdent; !reflect.DeepEqual(got, want) { + t.Errorf("got witness sigident\n\t%v\nbut wanted\n\t%v\nin test %q", got, want, table.description) + continue + } + + // Adding a duplicate signature should give an error + if err := sm.AddCosignature(context.Background(), table.vk, witnessSigIdent.Signature); err == nil { + t.Errorf("duplicate witness signature accepted as valid") + } + } +} + +func TestRotate(t *testing.T) { + log := testSigIdent + wit1 := &types.SigIdent{ + Signature: testSig, + KeyHash: types.Hash([]byte("wit1 key")), + } + wit2 := &types.SigIdent{ + Signature: testSig, + KeyHash: types.Hash([]byte("wit2 key")), + } + th0 := testTH + th1 := &types.TreeHead{ + Timestamp: 1, + TreeSize: 1, + RootHash: types.Hash([]byte("1")), + } + th2 := &types.TreeHead{ + Timestamp: 2, + TreeSize: 2, + RootHash: types.Hash([]byte("2")), + } + + for _, table := range []struct { + description string + before, after *StateManagerSingle + next *types.SignedTreeHead + }{ + { + description: "tosign tree head repated, but got one new witnes signature", + before: &StateManagerSingle{ + cosigned: types.SignedTreeHead{ + TreeHead: *th0, + SigIdent: []*types.SigIdent{log, wit1}, + }, + tosign: types.SignedTreeHead{ + TreeHead: *th0, + SigIdent: []*types.SigIdent{log}, + }, + cosignature: map[[types.HashSize]byte]*types.SigIdent{ + *log.KeyHash: log, + *wit2.KeyHash: wit2, // the new witness signature + }, + }, + next: &types.SignedTreeHead{ + TreeHead: *th1, + SigIdent: []*types.SigIdent{log}, + }, + after: &StateManagerSingle{ + cosigned: types.SignedTreeHead{ + TreeHead: *th0, + SigIdent: []*types.SigIdent{log, wit1, wit2}, + }, + tosign: types.SignedTreeHead{ + TreeHead: *th1, + SigIdent: []*types.SigIdent{log}, + }, + cosignature: map[[types.HashSize]byte]*types.SigIdent{ + *log.KeyHash: log, // after rotate we always have log sig + }, + }, + }, + { + description: "tosign tree head did not repeat, it got one witness signature", + before: &StateManagerSingle{ + cosigned: types.SignedTreeHead{ + TreeHead: *th0, + SigIdent: []*types.SigIdent{log, wit1}, + }, + tosign: types.SignedTreeHead{ + TreeHead: *th1, + SigIdent: []*types.SigIdent{log}, + }, + cosignature: map[[types.HashSize]byte]*types.SigIdent{ + *log.KeyHash: log, + *wit2.KeyHash: wit2, // the only witness that signed tosign + }, + }, + next: &types.SignedTreeHead{ + TreeHead: *th2, + SigIdent: []*types.SigIdent{log}, + }, + after: &StateManagerSingle{ + cosigned: types.SignedTreeHead{ + TreeHead: *th1, + SigIdent: []*types.SigIdent{log, wit2}, + }, + tosign: types.SignedTreeHead{ + TreeHead: *th2, + SigIdent: []*types.SigIdent{log}, + }, + cosignature: map[[types.HashSize]byte]*types.SigIdent{ + *log.KeyHash: log, // after rotate we always have log sig + }, + }, + }, + } { + table.before.rotate(table.next) + if got, want := table.before.cosigned.TreeHead, table.after.cosigned.TreeHead; !reflect.DeepEqual(got, want) { + t.Errorf("got cosigned tree head\n\t%v\nbut wanted\n\t%v\nin test %q", got, want, table.description) + } + checkWitnessList(t, table.description, table.before.cosigned.SigIdent, table.after.cosigned.SigIdent) + if got, want := table.before.tosign.TreeHead, table.after.tosign.TreeHead; !reflect.DeepEqual(got, want) { + t.Errorf("got tosign tree head\n\t%v\nbut wanted\n\t%v\nin test %q", got, want, table.description) + } + checkWitnessList(t, table.description, table.before.tosign.SigIdent, table.after.tosign.SigIdent) + if got, want := table.before.cosignature, table.after.cosignature; !reflect.DeepEqual(got, want) { + t.Errorf("got cosignature map\n\t%v\nbut wanted\n\t%v\nin test %q", got, want, table.description) + } + } +} + +func checkWitnessList(t *testing.T, description string, got, want []*types.SigIdent) { + t.Helper() + for _, si := range got { + found := false + for _, sj := range want { + if reflect.DeepEqual(si, sj) { + found = true + break + } + } + if !found { + t.Errorf("got unexpected signature-signer pair with key hash in test %q: %x", description, si.KeyHash[:]) + } + } + if len(got) != len(want) { + t.Errorf("got %d signature-signer pairs but wanted %d in test %q", len(got), len(want), description) + } +} diff --git a/pkg/trillian/client.go b/pkg/trillian/client.go new file mode 100644 index 0000000..9523e56 --- /dev/null +++ b/pkg/trillian/client.go @@ -0,0 +1,178 @@ +package trillian + +import ( + "context" + "fmt" + + "github.com/golang/glog" + "github.com/google/trillian" + ttypes "github.com/google/trillian/types" + "github.com/system-transparency/stfe/pkg/types" + "google.golang.org/grpc/codes" +) + +type Client interface { + AddLeaf(context.Context, *types.LeafRequest) error + GetConsistencyProof(context.Context, *types.ConsistencyProofRequest) (*types.ConsistencyProof, error) + GetTreeHead(context.Context) (*types.TreeHead, error) + GetInclusionProof(context.Context, *types.InclusionProofRequest) (*types.InclusionProof, error) + GetLeaves(context.Context, *types.LeavesRequest) (*types.LeafList, error) +} + +// TrillianClient is a wrapper around the Trillian gRPC client. +type TrillianClient struct { + // TreeID is a Merkle tree identifier that Trillian uses + TreeID int64 + + // GRPC is a Trillian gRPC client + GRPC trillian.TrillianLogClient +} + +func (c *TrillianClient) AddLeaf(ctx context.Context, req *types.LeafRequest) error { + leaf := types.Leaf{ + Message: req.Message, + SigIdent: types.SigIdent{ + Signature: req.Signature, + KeyHash: types.Hash(req.VerificationKey[:]), + }, + } + serialized := leaf.Marshal() + + glog.V(3).Infof("queueing leaf request: %x", types.HashLeaf(serialized)) + rsp, err := c.GRPC.QueueLeaf(ctx, &trillian.QueueLeafRequest{ + LogId: c.TreeID, + Leaf: &trillian.LogLeaf{ + LeafValue: serialized, + }, + }) + if err != nil { + return fmt.Errorf("backend failure: %v", err) + } + if rsp == nil { + return fmt.Errorf("no response") + } + if rsp.QueuedLeaf == nil { + return fmt.Errorf("no queued leaf") + } + if codes.Code(rsp.QueuedLeaf.GetStatus().GetCode()) == codes.AlreadyExists { + return fmt.Errorf("leaf is already queued or included") + } + return nil +} + +func (c *TrillianClient) GetTreeHead(ctx context.Context) (*types.TreeHead, error) { + rsp, err := c.GRPC.GetLatestSignedLogRoot(ctx, &trillian.GetLatestSignedLogRootRequest{ + LogId: c.TreeID, + }) + if err != nil { + return nil, fmt.Errorf("backend failure: %v", err) + } + if rsp == nil { + return nil, fmt.Errorf("no response") + } + if rsp.SignedLogRoot == nil { + return nil, fmt.Errorf("no signed log root") + } + if rsp.SignedLogRoot.LogRoot == nil { + return nil, fmt.Errorf("no log root") + } + var r ttypes.LogRootV1 + if err := r.UnmarshalBinary(rsp.SignedLogRoot.LogRoot); err != nil { + return nil, fmt.Errorf("no log root: unmarshal failed: %v", err) + } + if len(r.RootHash) != types.HashSize { + return nil, fmt.Errorf("unexpected hash length: %d", len(r.RootHash)) + } + return treeHeadFromLogRoot(&r), nil +} + +func (c *TrillianClient) GetConsistencyProof(ctx context.Context, req *types.ConsistencyProofRequest) (*types.ConsistencyProof, error) { + rsp, err := c.GRPC.GetConsistencyProof(ctx, &trillian.GetConsistencyProofRequest{ + LogId: c.TreeID, + FirstTreeSize: int64(req.OldSize), + SecondTreeSize: int64(req.NewSize), + }) + if err != nil { + return nil, fmt.Errorf("backend failure: %v", err) + } + if rsp == nil { + return nil, fmt.Errorf("no response") + } + if rsp.Proof == nil { + return nil, fmt.Errorf("no consistency proof") + } + if len(rsp.Proof.Hashes) == 0 { + return nil, fmt.Errorf("not a consistency proof: empty") + } + path, err := nodePathFromHashes(rsp.Proof.Hashes) + if err != nil { + return nil, fmt.Errorf("not a consistency proof: %v", err) + } + return &types.ConsistencyProof{ + OldSize: req.OldSize, + NewSize: req.NewSize, + Path: path, + }, nil +} + +func (c *TrillianClient) GetInclusionProof(ctx context.Context, req *types.InclusionProofRequest) (*types.InclusionProof, error) { + rsp, err := c.GRPC.GetInclusionProofByHash(ctx, &trillian.GetInclusionProofByHashRequest{ + LogId: c.TreeID, + LeafHash: req.LeafHash[:], + TreeSize: int64(req.TreeSize), + OrderBySequence: true, + }) + if err != nil { + return nil, fmt.Errorf("backend failure: %v", err) + } + if rsp == nil { + return nil, fmt.Errorf("no response") + } + if len(rsp.Proof) != 1 { + return nil, fmt.Errorf("bad proof count: %d", len(rsp.Proof)) + } + proof := rsp.Proof[0] + if len(proof.Hashes) == 0 { + return nil, fmt.Errorf("not an inclusion proof: empty") + } + path, err := nodePathFromHashes(proof.Hashes) + if err != nil { + return nil, fmt.Errorf("not an inclusion proof: %v", err) + } + return &types.InclusionProof{ + TreeSize: req.TreeSize, + LeafIndex: uint64(proof.LeafIndex), + Path: path, + }, nil +} + +func (c *TrillianClient) GetLeaves(ctx context.Context, req *types.LeavesRequest) (*types.LeafList, error) { + rsp, err := c.GRPC.GetLeavesByRange(ctx, &trillian.GetLeavesByRangeRequest{ + LogId: c.TreeID, + StartIndex: int64(req.StartSize), + Count: int64(req.EndSize-req.StartSize) + 1, + }) + if err != nil { + return nil, fmt.Errorf("backend failure: %v", err) + } + if rsp == nil { + return nil, fmt.Errorf("no response") + } + if got, want := len(rsp.Leaves), int(req.EndSize-req.StartSize+1); got != want { + return nil, fmt.Errorf("unexpected number of leaves: %d", got) + } + var list types.LeafList + for i, leaf := range rsp.Leaves { + leafIndex := int64(req.StartSize + uint64(i)) + if leafIndex != leaf.LeafIndex { + return nil, fmt.Errorf("unexpected leaf(%d): got index %d", leafIndex, leaf.LeafIndex) + } + + var l types.Leaf + if err := l.Unmarshal(leaf.LeafValue); err != nil { + return nil, fmt.Errorf("unexpected leaf(%d): %v", leafIndex, err) + } + list = append(list[:], &l) + } + return &list, nil +} diff --git a/pkg/trillian/client_test.go b/pkg/trillian/client_test.go new file mode 100644 index 0000000..6b3d881 --- /dev/null +++ b/pkg/trillian/client_test.go @@ -0,0 +1,533 @@ +package trillian + +import ( + "context" + "fmt" + "reflect" + "testing" + + "github.com/golang/mock/gomock" + "github.com/google/trillian" + ttypes "github.com/google/trillian/types" + "github.com/system-transparency/stfe/pkg/mocks" + "github.com/system-transparency/stfe/pkg/types" + "google.golang.org/grpc/codes" + "google.golang.org/grpc/status" +) + +func TestAddLeaf(t *testing.T) { + req := &types.LeafRequest{ + Message: types.Message{ + ShardHint: 0, + Checksum: &[types.HashSize]byte{}, + }, + Signature: &[types.SignatureSize]byte{}, + VerificationKey: &[types.VerificationKeySize]byte{}, + DomainHint: "example.com", + } + for _, table := range []struct { + description string + req *types.LeafRequest + rsp *trillian.QueueLeafResponse + err error + wantErr bool + }{ + { + description: "invalid: backend failure", + req: req, + err: fmt.Errorf("something went wrong"), + wantErr: true, + }, + { + description: "invalid: no response", + req: req, + wantErr: true, + }, + { + description: "invalid: no queued leaf", + req: req, + rsp: &trillian.QueueLeafResponse{}, + wantErr: true, + }, + { + description: "invalid: leaf is already queued or included", + req: req, + rsp: &trillian.QueueLeafResponse{ + QueuedLeaf: &trillian.QueuedLogLeaf{ + Leaf: &trillian.LogLeaf{ + LeafValue: req.Message.Marshal(), + }, + Status: status.New(codes.AlreadyExists, "duplicate").Proto(), + }, + }, + wantErr: true, + }, + { + description: "valid", + req: req, + rsp: &trillian.QueueLeafResponse{ + QueuedLeaf: &trillian.QueuedLogLeaf{ + Leaf: &trillian.LogLeaf{ + LeafValue: req.Message.Marshal(), + }, + Status: status.New(codes.OK, "ok").Proto(), + }, + }, + }, + } { + // Run deferred functions at the end of each iteration + func() { + ctrl := gomock.NewController(t) + defer ctrl.Finish() + grpc := mocks.NewMockTrillianLogClient(ctrl) + grpc.EXPECT().QueueLeaf(gomock.Any(), gomock.Any()).Return(table.rsp, table.err) + client := TrillianClient{GRPC: grpc} + + err := client.AddLeaf(context.Background(), table.req) + if got, want := err != nil, table.wantErr; got != want { + t.Errorf("got error %v but wanted %v in test %q: %v", got, want, table.description, err) + } + }() + } +} + +func TestGetTreeHead(t *testing.T) { + // valid root + root := &ttypes.LogRootV1{ + TreeSize: 0, + RootHash: make([]byte, types.HashSize), + TimestampNanos: 1622585623133599429, + } + buf, err := root.MarshalBinary() + if err != nil { + t.Fatalf("must marshal log root: %v", err) + } + // invalid root + root.RootHash = make([]byte, types.HashSize+1) + bufBadHash, err := root.MarshalBinary() + if err != nil { + t.Fatalf("must marshal log root: %v", err) + } + + for _, table := range []struct { + description string + rsp *trillian.GetLatestSignedLogRootResponse + err error + wantErr bool + wantTh *types.TreeHead + }{ + { + description: "invalid: backend failure", + err: fmt.Errorf("something went wrong"), + wantErr: true, + }, + { + description: "invalid: no response", + wantErr: true, + }, + { + description: "invalid: no signed log root", + rsp: &trillian.GetLatestSignedLogRootResponse{}, + wantErr: true, + }, + { + description: "invalid: no log root", + rsp: &trillian.GetLatestSignedLogRootResponse{ + SignedLogRoot: &trillian.SignedLogRoot{}, + }, + wantErr: true, + }, + { + description: "invalid: no log root: unmarshal failed", + rsp: &trillian.GetLatestSignedLogRootResponse{ + SignedLogRoot: &trillian.SignedLogRoot{ + LogRoot: buf[1:], + }, + }, + wantErr: true, + }, + { + description: "invalid: unexpected hash length", + rsp: &trillian.GetLatestSignedLogRootResponse{ + SignedLogRoot: &trillian.SignedLogRoot{ + LogRoot: bufBadHash, + }, + }, + wantErr: true, + }, + { + description: "valid", + rsp: &trillian.GetLatestSignedLogRootResponse{ + SignedLogRoot: &trillian.SignedLogRoot{ + LogRoot: buf, + }, + }, + wantTh: &types.TreeHead{ + Timestamp: 1622585623, + TreeSize: 0, + RootHash: &[types.HashSize]byte{}, + }, + }, + } { + // Run deferred functions at the end of each iteration + func() { + ctrl := gomock.NewController(t) + defer ctrl.Finish() + grpc := mocks.NewMockTrillianLogClient(ctrl) + grpc.EXPECT().GetLatestSignedLogRoot(gomock.Any(), gomock.Any()).Return(table.rsp, table.err) + client := TrillianClient{GRPC: grpc} + + th, err := client.GetTreeHead(context.Background()) + if got, want := err != nil, table.wantErr; got != want { + t.Errorf("got error %v but wanted %v in test %q: %v", got, want, table.description, err) + } + if err != nil { + return + } + if got, want := th, table.wantTh; !reflect.DeepEqual(got, want) { + t.Errorf("got tree head\n\t%v\nbut wanted\n\t%v\nin test %q", got, want, table.description) + } + }() + } +} + +func TestGetConsistencyProof(t *testing.T) { + req := &types.ConsistencyProofRequest{ + OldSize: 1, + NewSize: 3, + } + for _, table := range []struct { + description string + req *types.ConsistencyProofRequest + rsp *trillian.GetConsistencyProofResponse + err error + wantErr bool + wantProof *types.ConsistencyProof + }{ + { + description: "invalid: backend failure", + req: req, + err: fmt.Errorf("something went wrong"), + wantErr: true, + }, + { + description: "invalid: no response", + req: req, + wantErr: true, + }, + { + description: "invalid: no consistency proof", + req: req, + rsp: &trillian.GetConsistencyProofResponse{}, + wantErr: true, + }, + { + description: "invalid: not a consistency proof (1/2)", + req: req, + rsp: &trillian.GetConsistencyProofResponse{ + Proof: &trillian.Proof{ + Hashes: [][]byte{}, + }, + }, + wantErr: true, + }, + { + description: "invalid: not a consistency proof (2/2)", + req: req, + rsp: &trillian.GetConsistencyProofResponse{ + Proof: &trillian.Proof{ + Hashes: [][]byte{ + make([]byte, types.HashSize), + make([]byte, types.HashSize+1), + }, + }, + }, + wantErr: true, + }, + { + description: "valid", + req: req, + rsp: &trillian.GetConsistencyProofResponse{ + Proof: &trillian.Proof{ + Hashes: [][]byte{ + make([]byte, types.HashSize), + make([]byte, types.HashSize), + }, + }, + }, + wantProof: &types.ConsistencyProof{ + OldSize: 1, + NewSize: 3, + Path: []*[types.HashSize]byte{ + &[types.HashSize]byte{}, + &[types.HashSize]byte{}, + }, + }, + }, + } { + // Run deferred functions at the end of each iteration + func() { + ctrl := gomock.NewController(t) + defer ctrl.Finish() + grpc := mocks.NewMockTrillianLogClient(ctrl) + grpc.EXPECT().GetConsistencyProof(gomock.Any(), gomock.Any()).Return(table.rsp, table.err) + client := TrillianClient{GRPC: grpc} + + proof, err := client.GetConsistencyProof(context.Background(), table.req) + if got, want := err != nil, table.wantErr; got != want { + t.Errorf("got error %v but wanted %v in test %q: %v", got, want, table.description, err) + } + if err != nil { + return + } + if got, want := proof, table.wantProof; !reflect.DeepEqual(got, want) { + t.Errorf("got proof\n\t%v\nbut wanted\n\t%v\nin test %q", got, want, table.description) + } + }() + } +} + +func TestGetInclusionProof(t *testing.T) { + req := &types.InclusionProofRequest{ + TreeSize: 4, + LeafHash: &[types.HashSize]byte{}, + } + for _, table := range []struct { + description string + req *types.InclusionProofRequest + rsp *trillian.GetInclusionProofByHashResponse + err error + wantErr bool + wantProof *types.InclusionProof + }{ + { + description: "invalid: backend failure", + req: req, + err: fmt.Errorf("something went wrong"), + wantErr: true, + }, + { + description: "invalid: no response", + req: req, + wantErr: true, + }, + { + description: "invalid: bad proof count", + req: req, + rsp: &trillian.GetInclusionProofByHashResponse{ + Proof: []*trillian.Proof{ + &trillian.Proof{}, + &trillian.Proof{}, + }, + }, + wantErr: true, + }, + { + description: "invalid: not an inclusion proof (1/2)", + req: req, + rsp: &trillian.GetInclusionProofByHashResponse{ + Proof: []*trillian.Proof{ + &trillian.Proof{ + LeafIndex: 1, + Hashes: [][]byte{}, + }, + }, + }, + wantErr: true, + }, + { + description: "invalid: not an inclusion proof (2/2)", + req: req, + rsp: &trillian.GetInclusionProofByHashResponse{ + Proof: []*trillian.Proof{ + &trillian.Proof{ + LeafIndex: 1, + Hashes: [][]byte{ + make([]byte, types.HashSize), + make([]byte, types.HashSize+1), + }, + }, + }, + }, + wantErr: true, + }, + { + description: "valid", + req: req, + rsp: &trillian.GetInclusionProofByHashResponse{ + Proof: []*trillian.Proof{ + &trillian.Proof{ + LeafIndex: 1, + Hashes: [][]byte{ + make([]byte, types.HashSize), + make([]byte, types.HashSize), + }, + }, + }, + }, + wantProof: &types.InclusionProof{ + TreeSize: 4, + LeafIndex: 1, + Path: []*[types.HashSize]byte{ + &[types.HashSize]byte{}, + &[types.HashSize]byte{}, + }, + }, + }, + } { + // Run deferred functions at the end of each iteration + func() { + ctrl := gomock.NewController(t) + defer ctrl.Finish() + grpc := mocks.NewMockTrillianLogClient(ctrl) + grpc.EXPECT().GetInclusionProofByHash(gomock.Any(), gomock.Any()).Return(table.rsp, table.err) + client := TrillianClient{GRPC: grpc} + + proof, err := client.GetInclusionProof(context.Background(), table.req) + if got, want := err != nil, table.wantErr; got != want { + t.Errorf("got error %v but wanted %v in test %q: %v", got, want, table.description, err) + } + if err != nil { + return + } + if got, want := proof, table.wantProof; !reflect.DeepEqual(got, want) { + t.Errorf("got proof\n\t%v\nbut wanted\n\t%v\nin test %q", got, want, table.description) + } + }() + } +} + +func TestGetLeaves(t *testing.T) { + req := &types.LeavesRequest{ + StartSize: 1, + EndSize: 2, + } + firstLeaf := &types.Leaf{ + Message: types.Message{ + ShardHint: 0, + Checksum: &[types.HashSize]byte{}, + }, + SigIdent: types.SigIdent{ + Signature: &[types.SignatureSize]byte{}, + KeyHash: &[types.HashSize]byte{}, + }, + } + secondLeaf := &types.Leaf{ + Message: types.Message{ + ShardHint: 0, + Checksum: &[types.HashSize]byte{}, + }, + SigIdent: types.SigIdent{ + Signature: &[types.SignatureSize]byte{}, + KeyHash: &[types.HashSize]byte{}, + }, + } + + for _, table := range []struct { + description string + req *types.LeavesRequest + rsp *trillian.GetLeavesByRangeResponse + err error + wantErr bool + wantLeaves *types.LeafList + }{ + { + description: "invalid: backend failure", + req: req, + err: fmt.Errorf("something went wrong"), + wantErr: true, + }, + { + description: "invalid: no response", + req: req, + wantErr: true, + }, + { + description: "invalid: unexpected number of leaves", + req: req, + rsp: &trillian.GetLeavesByRangeResponse{ + Leaves: []*trillian.LogLeaf{ + &trillian.LogLeaf{ + LeafValue: firstLeaf.Marshal(), + LeafIndex: 1, + }, + }, + }, + wantErr: true, + }, + { + description: "invalid: unexpected leaf (1/2)", + req: req, + rsp: &trillian.GetLeavesByRangeResponse{ + Leaves: []*trillian.LogLeaf{ + &trillian.LogLeaf{ + LeafValue: firstLeaf.Marshal(), + LeafIndex: 1, + }, + &trillian.LogLeaf{ + LeafValue: secondLeaf.Marshal(), + LeafIndex: 3, + }, + }, + }, + wantErr: true, + }, + { + description: "invalid: unexpected leaf (2/2)", + req: req, + rsp: &trillian.GetLeavesByRangeResponse{ + Leaves: []*trillian.LogLeaf{ + &trillian.LogLeaf{ + LeafValue: firstLeaf.Marshal(), + LeafIndex: 1, + }, + &trillian.LogLeaf{ + LeafValue: secondLeaf.Marshal()[1:], + LeafIndex: 2, + }, + }, + }, + wantErr: true, + }, + { + description: "valid", + req: req, + rsp: &trillian.GetLeavesByRangeResponse{ + Leaves: []*trillian.LogLeaf{ + &trillian.LogLeaf{ + LeafValue: firstLeaf.Marshal(), + LeafIndex: 1, + }, + &trillian.LogLeaf{ + LeafValue: secondLeaf.Marshal(), + LeafIndex: 2, + }, + }, + }, + wantLeaves: &types.LeafList{ + firstLeaf, + secondLeaf, + }, + }, + } { + // Run deferred functions at the end of each iteration + func() { + ctrl := gomock.NewController(t) + defer ctrl.Finish() + grpc := mocks.NewMockTrillianLogClient(ctrl) + grpc.EXPECT().GetLeavesByRange(gomock.Any(), gomock.Any()).Return(table.rsp, table.err) + client := TrillianClient{GRPC: grpc} + + leaves, err := client.GetLeaves(context.Background(), table.req) + if got, want := err != nil, table.wantErr; got != want { + t.Errorf("got error %v but wanted %v in test %q: %v", got, want, table.description, err) + } + if err != nil { + return + } + if got, want := leaves, table.wantLeaves; !reflect.DeepEqual(got, want) { + t.Errorf("got leaves\n\t%v\nbut wanted\n\t%v\nin test %q", got, want, table.description) + } + }() + } +} diff --git a/pkg/trillian/util.go b/pkg/trillian/util.go new file mode 100644 index 0000000..4cf31fb --- /dev/null +++ b/pkg/trillian/util.go @@ -0,0 +1,33 @@ +package trillian + +import ( + "fmt" + + trillian "github.com/google/trillian/types" + siglog "github.com/system-transparency/stfe/pkg/types" +) + +func treeHeadFromLogRoot(lr *trillian.LogRootV1) *siglog.TreeHead { + var hash [siglog.HashSize]byte + th := siglog.TreeHead{ + Timestamp: uint64(lr.TimestampNanos / 1000 / 1000 / 1000), + TreeSize: uint64(lr.TreeSize), + RootHash: &hash, + } + copy(th.RootHash[:], lr.RootHash) + return &th +} + +func nodePathFromHashes(hashes [][]byte) ([]*[siglog.HashSize]byte, error) { + var path []*[siglog.HashSize]byte + for _, hash := range hashes { + if len(hash) != siglog.HashSize { + return nil, fmt.Errorf("unexpected hash length: %v", len(hash)) + } + + var h [siglog.HashSize]byte + copy(h[:], hash) + path = append(path, &h) + } + return path, nil +} diff --git a/pkg/types/ascii.go b/pkg/types/ascii.go new file mode 100644 index 0000000..d27d79b --- /dev/null +++ b/pkg/types/ascii.go @@ -0,0 +1,421 @@ +package types + +import ( + "bytes" + "encoding/hex" + "fmt" + "io" + "io/ioutil" + "strconv" +) + +const ( + // Delim is a key-value separator + Delim = "=" + + // EOL is a line sepator + EOL = "\n" + + // NumField* is the number of unique keys in an incoming ASCII message + NumFieldLeaf = 4 + NumFieldSignedTreeHead = 5 + NumFieldConsistencyProof = 3 + NumFieldInclusionProof = 3 + NumFieldLeavesRequest = 2 + NumFieldInclusionProofRequest = 2 + NumFieldConsistencyProofRequest = 2 + NumFieldLeafRequest = 5 + NumFieldCosignatureRequest = 2 + + // New leaf keys + ShardHint = "shard_hint" + Checksum = "checksum" + SignatureOverMessage = "signature_over_message" + VerificationKey = "verification_key" + DomainHint = "domain_hint" + + // Inclusion proof keys + LeafHash = "leaf_hash" + LeafIndex = "leaf_index" + InclusionPath = "inclusion_path" + + // Consistency proof keys + NewSize = "new_size" + OldSize = "old_size" + ConsistencyPath = "consistency_path" + + // Range of leaves keys + StartSize = "start_size" + EndSize = "end_size" + + // Tree head keys + Timestamp = "timestamp" + TreeSize = "tree_size" + RootHash = "root_hash" + + // Signature and signer-identity keys + Signature = "signature" + KeyHash = "key_hash" +) + +// MessageASCI is a wrapper that manages ASCII key-value pairs +type MessageASCII struct { + m map[string][]string +} + +// NewMessageASCII unpacks an incoming ASCII message +func NewMessageASCII(r io.Reader, numFieldExpected int) (*MessageASCII, error) { + buf, err := ioutil.ReadAll(r) + if err != nil { + return nil, fmt.Errorf("ReadAll: %v", err) + } + lines := bytes.Split(buf, []byte(EOL)) + if len(lines) <= 1 { + return nil, fmt.Errorf("Not enough lines: empty") + } + lines = lines[:len(lines)-1] // valid message => split gives empty last line + + msg := MessageASCII{make(map[string][]string)} + for _, line := range lines { + split := bytes.Index(line, []byte(Delim)) + if split == -1 { + return nil, fmt.Errorf("invalid line: %v", string(line)) + } + + key := string(line[:split]) + value := string(line[split+len(Delim):]) + values, ok := msg.m[key] + if !ok { + values = nil + msg.m[key] = values + } + msg.m[key] = append(values, value) + } + + if msg.NumField() != numFieldExpected { + return nil, fmt.Errorf("Unexpected number of keys: %v", msg.NumField()) + } + return &msg, nil +} + +// NumField returns the number of unique keys +func (msg *MessageASCII) NumField() int { + return len(msg.m) +} + +// GetStrings returns a list of strings +func (msg *MessageASCII) GetStrings(key string) []string { + strs, ok := msg.m[key] + if !ok { + return nil + } + return strs +} + +// GetString unpacks a string +func (msg *MessageASCII) GetString(key string) (string, error) { + strs := msg.GetStrings(key) + if len(strs) != 1 { + return "", fmt.Errorf("expected one string: %v", strs) + } + return strs[0], nil +} + +// GetUint64 unpacks an uint64 +func (msg *MessageASCII) GetUint64(key string) (uint64, error) { + str, err := msg.GetString(key) + if err != nil { + return 0, fmt.Errorf("GetString: %v", err) + } + num, err := strconv.ParseUint(str, 10, 64) + if err != nil { + return 0, fmt.Errorf("ParseUint: %v", err) + } + return num, nil +} + +// GetHash unpacks a hash +func (msg *MessageASCII) GetHash(key string) (*[HashSize]byte, error) { + str, err := msg.GetString(key) + if err != nil { + return nil, fmt.Errorf("GetString: %v", err) + } + + var hash [HashSize]byte + if err := decodeHex(str, hash[:]); err != nil { + return nil, fmt.Errorf("decodeHex: %v", err) + } + return &hash, nil +} + +// GetSignature unpacks a signature +func (msg *MessageASCII) GetSignature(key string) (*[SignatureSize]byte, error) { + str, err := msg.GetString(key) + if err != nil { + return nil, fmt.Errorf("GetString: %v", err) + } + + var signature [SignatureSize]byte + if err := decodeHex(str, signature[:]); err != nil { + return nil, fmt.Errorf("decodeHex: %v", err) + } + return &signature, nil +} + +// GetVerificationKey unpacks a verification key +func (msg *MessageASCII) GetVerificationKey(key string) (*[VerificationKeySize]byte, error) { + str, err := msg.GetString(key) + if err != nil { + return nil, fmt.Errorf("GetString: %v", err) + } + + var vk [VerificationKeySize]byte + if err := decodeHex(str, vk[:]); err != nil { + return nil, fmt.Errorf("decodeHex: %v", err) + } + return &vk, nil +} + +// decodeHex decodes a hex-encoded string into an already-sized byte slice +func decodeHex(str string, out []byte) error { + buf, err := hex.DecodeString(str) + if err != nil { + return fmt.Errorf("DecodeString: %v", err) + } + if len(buf) != len(out) { + return fmt.Errorf("invalid length: %v", len(buf)) + } + copy(out, buf) + return nil +} + +/* + * + * MarshalASCII wrappers for types that the log server outputs + * + */ +func (l *Leaf) MarshalASCII(w io.Writer) error { + if err := writeASCII(w, ShardHint, strconv.FormatUint(l.ShardHint, 10)); err != nil { + return fmt.Errorf("writeASCII: %v", err) + } + if err := writeASCII(w, Checksum, hex.EncodeToString(l.Checksum[:])); err != nil { + return fmt.Errorf("writeASCII: %v", err) + } + if err := writeASCII(w, SignatureOverMessage, hex.EncodeToString(l.Signature[:])); err != nil { + return fmt.Errorf("writeASCII: %v", err) + } + if err := writeASCII(w, KeyHash, hex.EncodeToString(l.KeyHash[:])); err != nil { + return fmt.Errorf("writeASCII: %v", err) + } + return nil +} + +func (sth *SignedTreeHead) MarshalASCII(w io.Writer) error { + if err := writeASCII(w, Timestamp, strconv.FormatUint(sth.Timestamp, 10)); err != nil { + return fmt.Errorf("writeASCII: %v", err) + } + if err := writeASCII(w, TreeSize, strconv.FormatUint(sth.TreeSize, 10)); err != nil { + return fmt.Errorf("writeASCII: %v", err) + } + if err := writeASCII(w, RootHash, hex.EncodeToString(sth.RootHash[:])); err != nil { + return fmt.Errorf("writeASCII: %v", err) + } + for _, sigident := range sth.SigIdent { + if err := sigident.MarshalASCII(w); err != nil { + return fmt.Errorf("MarshalASCII: %v", err) + } + } + return nil +} + +func (si *SigIdent) MarshalASCII(w io.Writer) error { + if err := writeASCII(w, Signature, hex.EncodeToString(si.Signature[:])); err != nil { + return fmt.Errorf("writeASCII: %v", err) + } + if err := writeASCII(w, KeyHash, hex.EncodeToString(si.KeyHash[:])); err != nil { + return fmt.Errorf("writeASCII: %v", err) + } + return nil +} + +func (p *ConsistencyProof) MarshalASCII(w io.Writer) error { + if err := writeASCII(w, NewSize, strconv.FormatUint(p.NewSize, 10)); err != nil { + return fmt.Errorf("writeASCII: %v", err) + } + if err := writeASCII(w, OldSize, strconv.FormatUint(p.OldSize, 10)); err != nil { + return fmt.Errorf("writeASCII: %v", err) + } + for _, hash := range p.Path { + if err := writeASCII(w, ConsistencyPath, hex.EncodeToString(hash[:])); err != nil { + return fmt.Errorf("writeASCII: %v", err) + } + } + return nil +} + +func (p *InclusionProof) MarshalASCII(w io.Writer) error { + if err := writeASCII(w, TreeSize, strconv.FormatUint(p.TreeSize, 10)); err != nil { + return fmt.Errorf("writeASCII: %v", err) + } + if err := writeASCII(w, LeafIndex, strconv.FormatUint(p.LeafIndex, 10)); err != nil { + return fmt.Errorf("writeASCII: %v", err) + } + for _, hash := range p.Path { + if err := writeASCII(w, InclusionPath, hex.EncodeToString(hash[:])); err != nil { + return fmt.Errorf("writeASCII: %v", err) + } + } + return nil +} + +func writeASCII(w io.Writer, key, value string) error { + if _, err := fmt.Fprintf(w, "%s%s%s%s", key, Delim, value, EOL); err != nil { + return fmt.Errorf("Fprintf: %v", err) + } + return nil +} + +/* + * + * Unmarshal ASCII wrappers that the log server and/or log clients receive. + * + */ +func (ll *LeafList) UnmarshalASCII(r io.Reader) error { + return nil +} + +func (sth *SignedTreeHead) UnmarshalASCII(r io.Reader) error { + msg, err := NewMessageASCII(r, NumFieldSignedTreeHead) + if err != nil { + return fmt.Errorf("NewMessageASCII: %v", err) + } + + // TreeHead + if sth.Timestamp, err = msg.GetUint64(Timestamp); err != nil { + return fmt.Errorf("GetUint64(Timestamp): %v", err) + } + if sth.TreeSize, err = msg.GetUint64(TreeSize); err != nil { + return fmt.Errorf("GetUint64(TreeSize): %v", err) + } + if sth.RootHash, err = msg.GetHash(RootHash); err != nil { + return fmt.Errorf("GetHash(RootHash): %v", err) + } + + // SigIdent + signatures := msg.GetStrings(Signature) + if len(signatures) == 0 { + return fmt.Errorf("no signer") + } + keyHashes := msg.GetStrings(KeyHash) + if len(signatures) != len(keyHashes) { + return fmt.Errorf("mismatched signature-signer count") + } + sth.SigIdent = make([]*SigIdent, 0, len(signatures)) + for i, n := 0, len(signatures); i < n; i++ { + var signature [SignatureSize]byte + if err := decodeHex(signatures[i], signature[:]); err != nil { + return fmt.Errorf("decodeHex: %v", err) + } + var hash [HashSize]byte + if err := decodeHex(keyHashes[i], hash[:]); err != nil { + return fmt.Errorf("decodeHex: %v", err) + } + sth.SigIdent = append(sth.SigIdent, &SigIdent{ + Signature: &signature, + KeyHash: &hash, + }) + } + return nil +} + +func (p *InclusionProof) UnmarshalASCII(r io.Reader) error { + return nil +} + +func (p *ConsistencyProof) UnmarshalASCII(r io.Reader) error { + return nil +} + +func (req *InclusionProofRequest) UnmarshalASCII(r io.Reader) error { + msg, err := NewMessageASCII(r, NumFieldInclusionProofRequest) + if err != nil { + return fmt.Errorf("NewMessageASCII: %v", err) + } + + if req.LeafHash, err = msg.GetHash(LeafHash); err != nil { + return fmt.Errorf("GetHash(LeafHash): %v", err) + } + if req.TreeSize, err = msg.GetUint64(TreeSize); err != nil { + return fmt.Errorf("GetUint64(TreeSize): %v", err) + } + return nil +} + +func (req *ConsistencyProofRequest) UnmarshalASCII(r io.Reader) error { + msg, err := NewMessageASCII(r, NumFieldConsistencyProofRequest) + if err != nil { + return fmt.Errorf("NewMessageASCII: %v", err) + } + + if req.NewSize, err = msg.GetUint64(NewSize); err != nil { + return fmt.Errorf("GetUint64(NewSize): %v", err) + } + if req.OldSize, err = msg.GetUint64(OldSize); err != nil { + return fmt.Errorf("GetUint64(OldSize): %v", err) + } + return nil +} + +func (req *LeavesRequest) UnmarshalASCII(r io.Reader) error { + msg, err := NewMessageASCII(r, NumFieldLeavesRequest) + if err != nil { + return fmt.Errorf("NewMessageASCII: %v", err) + } + + if req.StartSize, err = msg.GetUint64(StartSize); err != nil { + return fmt.Errorf("GetUint64(StartSize): %v", err) + } + if req.EndSize, err = msg.GetUint64(EndSize); err != nil { + return fmt.Errorf("GetUint64(EndSize): %v", err) + } + return nil +} + +func (req *LeafRequest) UnmarshalASCII(r io.Reader) error { + msg, err := NewMessageASCII(r, NumFieldLeafRequest) + if err != nil { + return fmt.Errorf("NewMessageASCII: %v", err) + } + + if req.ShardHint, err = msg.GetUint64(ShardHint); err != nil { + return fmt.Errorf("GetUint64(ShardHint): %v", err) + } + if req.Checksum, err = msg.GetHash(Checksum); err != nil { + return fmt.Errorf("GetHash(Checksum): %v", err) + } + if req.Signature, err = msg.GetSignature(SignatureOverMessage); err != nil { + return fmt.Errorf("GetSignature: %v", err) + } + if req.VerificationKey, err = msg.GetVerificationKey(VerificationKey); err != nil { + return fmt.Errorf("GetVerificationKey: %v", err) + } + if req.DomainHint, err = msg.GetString(DomainHint); err != nil { + return fmt.Errorf("GetString(DomainHint): %v", err) + } + return nil +} + +func (req *CosignatureRequest) UnmarshalASCII(r io.Reader) error { + msg, err := NewMessageASCII(r, NumFieldCosignatureRequest) + if err != nil { + return fmt.Errorf("NewMessageASCII: %v", err) + } + + if req.Signature, err = msg.GetSignature(Signature); err != nil { + return fmt.Errorf("GetSignature: %v", err) + } + if req.KeyHash, err = msg.GetHash(KeyHash); err != nil { + return fmt.Errorf("GetHash(KeyHash): %v", err) + } + return nil +} diff --git a/pkg/types/ascii_test.go b/pkg/types/ascii_test.go new file mode 100644 index 0000000..92732f9 --- /dev/null +++ b/pkg/types/ascii_test.go @@ -0,0 +1,465 @@ +package types + +import ( + "bytes" + "fmt" + "io" + "reflect" + "testing" +) + +/* + * + * MessageASCII methods and helpers + * + */ +func TestNewMessageASCII(t *testing.T) { + for _, table := range []struct { + description string + input io.Reader + wantErr bool + wantMap map[string][]string + }{ + { + description: "invalid: not enough lines", + input: bytes.NewBufferString(""), + wantErr: true, + }, + { + description: "invalid: lines must end with new line", + input: bytes.NewBufferString("k1=v1\nk2=v2"), + wantErr: true, + }, + { + description: "invalid: lines must not be empty", + input: bytes.NewBufferString("k1=v1\n\nk2=v2\n"), + wantErr: true, + }, + { + description: "invalid: wrong number of fields", + input: bytes.NewBufferString("k1=v1\n"), + wantErr: true, + }, + { + description: "valid", + input: bytes.NewBufferString("k1=v1\nk2=v2\nk2=v3=4\n"), + wantMap: map[string][]string{ + "k1": []string{"v1"}, + "k2": []string{"v2", "v3=4"}, + }, + }, + } { + msg, err := NewMessageASCII(table.input, len(table.wantMap)) + if got, want := err != nil, table.wantErr; got != want { + t.Errorf("got error %v but wanted %v in test %q: %v", got, want, table.description, err) + } + if err != nil { + continue + } + if got, want := msg.m, table.wantMap; !reflect.DeepEqual(got, want) { + t.Errorf("got\n\t%v\nbut wanted\n\t%v\nin test %q", got, want, table.description) + } + } +} + +func TestNumField(t *testing.T) {} +func TestGetStrings(t *testing.T) {} +func TestGetString(t *testing.T) {} +func TestGetUint64(t *testing.T) {} +func TestGetHash(t *testing.T) {} +func TestGetSignature(t *testing.T) {} +func TestGetVerificationKey(t *testing.T) {} +func TestDecodeHex(t *testing.T) {} + +/* + * + * MarshalASCII methods and helpers + * + */ +func TestLeafMarshalASCII(t *testing.T) { + description := "valid: two leaves" + leafList := []*Leaf{ + &Leaf{ + Message: Message{ + ShardHint: 123, + Checksum: testBuffer32, + }, + SigIdent: SigIdent{ + Signature: testBuffer64, + KeyHash: testBuffer32, + }, + }, + &Leaf{ + Message: Message{ + ShardHint: 456, + Checksum: testBuffer32, + }, + SigIdent: SigIdent{ + Signature: testBuffer64, + KeyHash: testBuffer32, + }, + }, + } + wantBuf := bytes.NewBufferString(fmt.Sprintf( + "%s%s%d%s"+"%s%s%x%s"+"%s%s%x%s"+"%s%s%x%s"+ + "%s%s%d%s"+"%s%s%x%s"+"%s%s%x%s"+"%s%s%x%s", + // Leaf 1 + ShardHint, Delim, 123, EOL, + Checksum, Delim, testBuffer32[:], EOL, + SignatureOverMessage, Delim, testBuffer64[:], EOL, + KeyHash, Delim, testBuffer32[:], EOL, + // Leaf 2 + ShardHint, Delim, 456, EOL, + Checksum, Delim, testBuffer32[:], EOL, + SignatureOverMessage, Delim, testBuffer64[:], EOL, + KeyHash, Delim, testBuffer32[:], EOL, + )) + buf := bytes.NewBuffer(nil) + for _, leaf := range leafList { + if err := leaf.MarshalASCII(buf); err != nil { + t.Errorf("expected error %v but got %v in test %q: %v", false, true, description, err) + return + } + } + if got, want := buf.Bytes(), wantBuf.Bytes(); !bytes.Equal(got, want) { + t.Errorf("got\n\t%v\nbut wanted\n\t%v\nin test %q", string(got), string(want), description) + } +} + +func TestSignedTreeHeadMarshalASCII(t *testing.T) { + description := "valid" + sth := &SignedTreeHead{ + TreeHead: TreeHead{ + Timestamp: 123, + TreeSize: 456, + RootHash: testBuffer32, + }, + SigIdent: []*SigIdent{ + &SigIdent{ + Signature: testBuffer64, + KeyHash: testBuffer32, + }, + &SigIdent{ + Signature: testBuffer64, + KeyHash: testBuffer32, + }, + }, + } + wantBuf := bytes.NewBufferString(fmt.Sprintf( + "%s%s%d%s"+"%s%s%d%s"+"%s%s%x%s"+"%s%s%x%s"+"%s%s%x%s"+"%s%s%x%s"+"%s%s%x%s", + Timestamp, Delim, 123, EOL, + TreeSize, Delim, 456, EOL, + RootHash, Delim, testBuffer32[:], EOL, + Signature, Delim, testBuffer64[:], EOL, + KeyHash, Delim, testBuffer32[:], EOL, + Signature, Delim, testBuffer64[:], EOL, + KeyHash, Delim, testBuffer32[:], EOL, + )) + buf := bytes.NewBuffer(nil) + if err := sth.MarshalASCII(buf); err != nil { + t.Errorf("expected error %v but got %v in test %q", false, true, description) + return + } + if got, want := buf.Bytes(), wantBuf.Bytes(); !bytes.Equal(got, want) { + t.Errorf("got\n\t%v\nbut wanted\n\t%v\nin test %q", string(got), string(want), description) + } +} + +func TestInclusionProofMarshalASCII(t *testing.T) { + description := "valid" + proof := InclusionProof{ + TreeSize: 321, + LeafIndex: 123, + Path: []*[HashSize]byte{ + testBuffer32, + testBuffer32, + }, + } + wantBuf := bytes.NewBufferString(fmt.Sprintf( + "%s%s%d%s"+"%s%s%d%s"+"%s%s%x%s"+"%s%s%x%s", + TreeSize, Delim, 321, EOL, + LeafIndex, Delim, 123, EOL, + InclusionPath, Delim, testBuffer32[:], EOL, + InclusionPath, Delim, testBuffer32[:], EOL, + )) + buf := bytes.NewBuffer(nil) + if err := proof.MarshalASCII(buf); err != nil { + t.Errorf("expected error %v but got %v in test %q", false, true, description) + return + } + if got, want := buf.Bytes(), wantBuf.Bytes(); !bytes.Equal(got, want) { + t.Errorf("got\n\t%v\nbut wanted\n\t%v\nin test %q", string(got), string(want), description) + } +} + +func TestConsistencyProofMarshalASCII(t *testing.T) { + description := "valid" + proof := ConsistencyProof{ + NewSize: 321, + OldSize: 123, + Path: []*[HashSize]byte{ + testBuffer32, + testBuffer32, + }, + } + wantBuf := bytes.NewBufferString(fmt.Sprintf( + "%s%s%d%s"+"%s%s%d%s"+"%s%s%x%s"+"%s%s%x%s", + NewSize, Delim, 321, EOL, + OldSize, Delim, 123, EOL, + ConsistencyPath, Delim, testBuffer32[:], EOL, + ConsistencyPath, Delim, testBuffer32[:], EOL, + )) + buf := bytes.NewBuffer(nil) + if err := proof.MarshalASCII(buf); err != nil { + t.Errorf("expected error %v but got %v in test %q", false, true, description) + return + } + if got, want := buf.Bytes(), wantBuf.Bytes(); !bytes.Equal(got, want) { + t.Errorf("got\n\t%v\nbut wanted\n\t%v\nin test %q", string(got), string(want), description) + } +} + +func TestWriteASCII(t *testing.T) { +} + +/* + * + * UnmarshalASCII methods and helpers + * + */ +func TestLeafListUnmarshalASCII(t *testing.T) {} + +func TestSignedTreeHeadUnmarshalASCII(t *testing.T) { + for _, table := range []struct { + description string + buf io.Reader + wantErr bool + wantSth *SignedTreeHead + }{ + { + description: "valid", + buf: bytes.NewBufferString(fmt.Sprintf( + "%s%s%d%s"+"%s%s%d%s"+"%s%s%x%s"+"%s%s%x%s"+"%s%s%x%s"+"%s%s%x%s"+"%s%s%x%s", + Timestamp, Delim, 123, EOL, + TreeSize, Delim, 456, EOL, + RootHash, Delim, testBuffer32[:], EOL, + Signature, Delim, testBuffer64[:], EOL, + KeyHash, Delim, testBuffer32[:], EOL, + Signature, Delim, testBuffer64[:], EOL, + KeyHash, Delim, testBuffer32[:], EOL, + )), + wantSth: &SignedTreeHead{ + TreeHead: TreeHead{ + Timestamp: 123, + TreeSize: 456, + RootHash: testBuffer32, + }, + SigIdent: []*SigIdent{ + &SigIdent{ + Signature: testBuffer64, + KeyHash: testBuffer32, + }, + &SigIdent{ + Signature: testBuffer64, + KeyHash: testBuffer32, + }, + }, + }, + }, + } { + var sth SignedTreeHead + err := sth.UnmarshalASCII(table.buf) + if got, want := err != nil, table.wantErr; got != want { + t.Errorf("got error %v but wanted %v in test %q: %v", got, want, table.description, err) + } + if err != nil { + continue + } + if got, want := &sth, table.wantSth; !reflect.DeepEqual(got, want) { + t.Errorf("got\n\t%v\nbut wanted\n\t%v\nin test %q", got, want, table.description) + } + } +} + +func TestInclusionProofUnmarshalASCII(t *testing.T) {} +func TestConsistencyProofUnmarshalASCII(t *testing.T) {} + +func TestInclusionProofRequestUnmarshalASCII(t *testing.T) { + for _, table := range []struct { + description string + buf io.Reader + wantErr bool + wantReq *InclusionProofRequest + }{ + { + description: "valid", + buf: bytes.NewBufferString(fmt.Sprintf( + "%s%s%x%s"+"%s%s%d%s", + LeafHash, Delim, testBuffer32[:], EOL, + TreeSize, Delim, 123, EOL, + )), + wantReq: &InclusionProofRequest{ + LeafHash: testBuffer32, + TreeSize: 123, + }, + }, + } { + var req InclusionProofRequest + err := req.UnmarshalASCII(table.buf) + if got, want := err != nil, table.wantErr; got != want { + t.Errorf("got error %v but wanted %v in test %q: %v", got, want, table.description, err) + } + if err != nil { + continue + } + if got, want := &req, table.wantReq; !reflect.DeepEqual(got, want) { + t.Errorf("got\n\t%v\nbut wanted\n\t%v\nin test %q", got, want, table.description) + } + } +} + +func TestConsistencyProofRequestUnmarshalASCII(t *testing.T) { + for _, table := range []struct { + description string + buf io.Reader + wantErr bool + wantReq *ConsistencyProofRequest + }{ + { + description: "valid", + buf: bytes.NewBufferString(fmt.Sprintf( + "%s%s%d%s"+"%s%s%d%s", + NewSize, Delim, 321, EOL, + OldSize, Delim, 123, EOL, + )), + wantReq: &ConsistencyProofRequest{ + NewSize: 321, + OldSize: 123, + }, + }, + } { + var req ConsistencyProofRequest + err := req.UnmarshalASCII(table.buf) + if got, want := err != nil, table.wantErr; got != want { + t.Errorf("got error %v but wanted %v in test %q: %v", got, want, table.description, err) + } + if err != nil { + continue + } + if got, want := &req, table.wantReq; !reflect.DeepEqual(got, want) { + t.Errorf("got\n\t%v\nbut wanted\n\t%v\nin test %q", got, want, table.description) + } + } +} + +func TestLeavesRequestUnmarshalASCII(t *testing.T) { + for _, table := range []struct { + description string + buf io.Reader + wantErr bool + wantReq *LeavesRequest + }{ + { + description: "valid", + buf: bytes.NewBufferString(fmt.Sprintf( + "%s%s%d%s"+"%s%s%d%s", + StartSize, Delim, 123, EOL, + EndSize, Delim, 456, EOL, + )), + wantReq: &LeavesRequest{ + StartSize: 123, + EndSize: 456, + }, + }, + } { + var req LeavesRequest + err := req.UnmarshalASCII(table.buf) + if got, want := err != nil, table.wantErr; got != want { + t.Errorf("got error %v but wanted %v in test %q: %v", got, want, table.description, err) + } + if err != nil { + continue + } + if got, want := &req, table.wantReq; !reflect.DeepEqual(got, want) { + t.Errorf("got\n\t%v\nbut wanted\n\t%v\nin test %q", got, want, table.description) + } + } +} + +func TestLeafRequestUnmarshalASCII(t *testing.T) { + for _, table := range []struct { + description string + buf io.Reader + wantErr bool + wantReq *LeafRequest + }{ + { + description: "valid", + buf: bytes.NewBufferString(fmt.Sprintf( + "%s%s%d%s"+"%s%s%x%s"+"%s%s%x%s"+"%s%s%x%s"+"%s%s%s%s", + ShardHint, Delim, 123, EOL, + Checksum, Delim, testBuffer32[:], EOL, + SignatureOverMessage, Delim, testBuffer64[:], EOL, + VerificationKey, Delim, testBuffer32[:], EOL, + DomainHint, Delim, "example.com", EOL, + )), + wantReq: &LeafRequest{ + Message: Message{ + ShardHint: 123, + Checksum: testBuffer32, + }, + Signature: testBuffer64, + VerificationKey: testBuffer32, + DomainHint: "example.com", + }, + }, + } { + var req LeafRequest + err := req.UnmarshalASCII(table.buf) + if got, want := err != nil, table.wantErr; got != want { + t.Errorf("got error %v but wanted %v in test %q: %v", got, want, table.description, err) + } + if err != nil { + continue + } + if got, want := &req, table.wantReq; !reflect.DeepEqual(got, want) { + t.Errorf("got\n\t%v\nbut wanted\n\t%v\nin test %q", got, want, table.description) + } + } +} + +func TestCosignatureRequestUnmarshalASCII(t *testing.T) { + for _, table := range []struct { + description string + buf io.Reader + wantErr bool + wantReq *CosignatureRequest + }{ + { + description: "valid", + buf: bytes.NewBufferString(fmt.Sprintf( + "%s%s%x%s"+"%s%s%x%s", + Signature, Delim, testBuffer64[:], EOL, + KeyHash, Delim, testBuffer32[:], EOL, + )), + wantReq: &CosignatureRequest{ + SigIdent: SigIdent{ + Signature: testBuffer64, + KeyHash: testBuffer32, + }, + }, + }, + } { + var req CosignatureRequest + err := req.UnmarshalASCII(table.buf) + if got, want := err != nil, table.wantErr; got != want { + t.Errorf("got error %v but wanted %v in test %q: %v", got, want, table.description, err) + } + if err != nil { + continue + } + if got, want := &req, table.wantReq; !reflect.DeepEqual(got, want) { + t.Errorf("got\n\t%v\nbut wanted\n\t%v\nin test %q", got, want, table.description) + } + } +} diff --git a/pkg/types/trunnel.go b/pkg/types/trunnel.go new file mode 100644 index 0000000..268f6f7 --- /dev/null +++ b/pkg/types/trunnel.go @@ -0,0 +1,60 @@ +package types + +import ( + "encoding/binary" + "fmt" +) + +const ( + // MessageSize is the number of bytes in a Trunnel-encoded leaf message + MessageSize = 8 + HashSize + // LeafSize is the number of bytes in a Trunnel-encoded leaf + LeafSize = MessageSize + SignatureSize + HashSize +) + +// Marshal returns a Trunnel-encoded message +func (m *Message) Marshal() []byte { + buf := make([]byte, MessageSize) + binary.BigEndian.PutUint64(buf, m.ShardHint) + copy(buf[8:], m.Checksum[:]) + return buf +} + +// Marshal returns a Trunnel-encoded leaf +func (l *Leaf) Marshal() []byte { + buf := l.Message.Marshal() + buf = append(buf, l.SigIdent.Signature[:]...) + buf = append(buf, l.SigIdent.KeyHash[:]...) + return buf +} + +// Marshal returns a Trunnel-encoded tree head +func (th *TreeHead) Marshal() []byte { + buf := make([]byte, 8+8+HashSize) + binary.BigEndian.PutUint64(buf[0:8], th.Timestamp) + binary.BigEndian.PutUint64(buf[8:16], th.TreeSize) + copy(buf[16:], th.RootHash[:]) + return buf +} + +// Unmarshal parses the Trunnel-encoded buffer as a leaf +func (l *Leaf) Unmarshal(buf []byte) error { + if len(buf) != LeafSize { + return fmt.Errorf("invalid leaf size: %v", len(buf)) + } + // Shard hint + l.ShardHint = binary.BigEndian.Uint64(buf) + offset := 8 + // Checksum + l.Checksum = &[HashSize]byte{} + copy(l.Checksum[:], buf[offset:offset+HashSize]) + offset += HashSize + // Signature + l.Signature = &[SignatureSize]byte{} + copy(l.Signature[:], buf[offset:offset+SignatureSize]) + offset += SignatureSize + // KeyHash + l.KeyHash = &[HashSize]byte{} + copy(l.KeyHash[:], buf[offset:]) + return nil +} diff --git a/pkg/types/trunnel_test.go b/pkg/types/trunnel_test.go new file mode 100644 index 0000000..297578c --- /dev/null +++ b/pkg/types/trunnel_test.go @@ -0,0 +1,114 @@ +package types + +import ( + "bytes" + "reflect" + "testing" +) + +var ( + testBuffer32 = &[32]byte{0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31} + testBuffer64 = &[64]byte{0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, 62, 63} +) + +func TestMarshalMessage(t *testing.T) { + description := "valid: shard hint 72623859790382856, checksum 0x00,0x01,..." + message := &Message{ + ShardHint: 72623859790382856, + Checksum: testBuffer32, + } + want := bytes.Join([][]byte{ + []byte{0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08}, + testBuffer32[:], + }, nil) + if got := message.Marshal(); !bytes.Equal(got, want) { + t.Errorf("got message\n\t%v\nbut wanted\n\t%v\nin test %q\n", got, want, description) + } +} + +func TestMarshalLeaf(t *testing.T) { + description := "valid: shard hint 72623859790382856, buffers 0x00,0x01,..." + leaf := &Leaf{ + Message: Message{ + ShardHint: 72623859790382856, + Checksum: testBuffer32, + }, + SigIdent: SigIdent{ + Signature: testBuffer64, + KeyHash: testBuffer32, + }, + } + want := bytes.Join([][]byte{ + []byte{0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08}, + testBuffer32[:], testBuffer64[:], testBuffer32[:], + }, nil) + if got := leaf.Marshal(); !bytes.Equal(got, want) { + t.Errorf("got leaf\n\t%v\nbut wanted\n\t%v\nin test %q\n", got, want, description) + } +} + +func TestMarshalTreeHead(t *testing.T) { + description := "valid: timestamp 16909060, tree size 72623859790382856, root hash 0x00,0x01,..." + th := &TreeHead{ + Timestamp: 16909060, + TreeSize: 72623859790382856, + RootHash: testBuffer32, + } + want := bytes.Join([][]byte{ + []byte{0x00, 0x00, 0x00, 0x00, 0x01, 0x02, 0x03, 0x04}, + []byte{0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08}, + testBuffer32[:], + }, nil) + if got := th.Marshal(); !bytes.Equal(got, want) { + t.Errorf("got tree head\n\t%v\nbut wanted\n\t%v\nin test %q\n", got, want, description) + } +} + +func TestUnmarshalLeaf(t *testing.T) { + for _, table := range []struct { + description string + serialized []byte + wantErr bool + want *Leaf + }{ + { + description: "invalid: not enough bytes", + serialized: make([]byte, LeafSize-1), + wantErr: true, + }, + { + description: "invalid: too many bytes", + serialized: make([]byte, LeafSize+1), + wantErr: true, + }, + { + description: "valid: shard hint 72623859790382856, buffers 0x00,0x01,...", + serialized: bytes.Join([][]byte{ + []byte{0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08}, + testBuffer32[:], testBuffer64[:], testBuffer32[:], + }, nil), + want: &Leaf{ + Message: Message{ + ShardHint: 72623859790382856, + Checksum: testBuffer32, + }, + SigIdent: SigIdent{ + Signature: testBuffer64, + KeyHash: testBuffer32, + }, + }, + }, + } { + var leaf Leaf + err := leaf.Unmarshal(table.serialized) + if got, want := err != nil, table.wantErr; got != want { + t.Errorf("got error %v but wanted %v in test %q: %v", got, want, table.description, err) + } + if err != nil { + continue + } + if got, want := &leaf, table.want; !reflect.DeepEqual(got, want) { + t.Errorf("got leaf\n\t%v\nbut wanted\n\t%v\nin test %q\n", got, want, table.description) + } + } +} diff --git a/pkg/types/types.go b/pkg/types/types.go new file mode 100644 index 0000000..9ca7db8 --- /dev/null +++ b/pkg/types/types.go @@ -0,0 +1,155 @@ +package types + +import ( + "crypto" + "crypto/ed25519" + "crypto/sha256" + "fmt" + "strings" +) + +const ( + HashSize = sha256.Size + SignatureSize = ed25519.SignatureSize + VerificationKeySize = ed25519.PublicKeySize + + EndpointAddLeaf = Endpoint("add-leaf") + EndpointAddCosignature = Endpoint("add-cosignature") + EndpointGetTreeHeadLatest = Endpoint("get-tree-head-latest") + EndpointGetTreeHeadToSign = Endpoint("get-tree-head-to-sign") + EndpointGetTreeHeadCosigned = Endpoint("get-tree-head-cosigned") + EndpointGetProofByHash = Endpoint("get-proof-by-hash") + EndpointGetConsistencyProof = Endpoint("get-consistency-proof") + EndpointGetLeaves = Endpoint("get-leaves") +) + +// Endpoint is a named HTTP API endpoint +type Endpoint string + +// Path joins a number of components to form a full endpoint path. For example, +// EndpointAddLeaf.Path("example.com", "st/v0") -> example.com/st/v0/add-leaf. +func (e Endpoint) Path(components ...string) string { + return strings.Join(append(components, string(e)), "/") +} + +// Leaf is the log's Merkle tree leaf. +type Leaf struct { + Message + SigIdent +} + +// Message is composed of a shard hint and a checksum. The submitter selects +// these values to fit the log's shard interval and the opaque data in question. +type Message struct { + ShardHint uint64 + Checksum *[HashSize]byte +} + +// SigIdent is composed of a signature-signer pair. The signature is computed +// over the Trunnel-serialized leaf message. KeyHash identifies the signer. +type SigIdent struct { + Signature *[SignatureSize]byte + KeyHash *[HashSize]byte +} + +// SignedTreeHead is composed of a tree head and a list of signature-signer +// pairs. Each signature is computed over the Trunnel-serialized tree head. +type SignedTreeHead struct { + TreeHead + SigIdent []*SigIdent +} + +// TreeHead is the log's tree head. +type TreeHead struct { + Timestamp uint64 + TreeSize uint64 + RootHash *[HashSize]byte +} + +// ConsistencyProof is a consistency proof that proves the log's append-only +// property. +type ConsistencyProof struct { + NewSize uint64 + OldSize uint64 + Path []*[HashSize]byte +} + +// InclusionProof is an inclusion proof that proves a leaf is included in the +// log. +type InclusionProof struct { + TreeSize uint64 + LeafIndex uint64 + Path []*[HashSize]byte +} + +// LeafList is a list of leaves +type LeafList []*Leaf + +// ConsistencyProofRequest is a get-consistency-proof request +type ConsistencyProofRequest struct { + NewSize uint64 + OldSize uint64 +} + +// InclusionProofRequest is a get-proof-by-hash request +type InclusionProofRequest struct { + LeafHash *[HashSize]byte + TreeSize uint64 +} + +// LeavesRequest is a get-leaves request +type LeavesRequest struct { + StartSize uint64 + EndSize uint64 +} + +// LeafRequest is an add-leaf request +type LeafRequest struct { + Message + Signature *[SignatureSize]byte + VerificationKey *[VerificationKeySize]byte + DomainHint string +} + +// CosignatureRequest is an add-cosignature request +type CosignatureRequest struct { + SigIdent +} + +// Sign signs the tree head using the log's signature scheme +func (th *TreeHead) Sign(signer crypto.Signer) (*SignedTreeHead, error) { + sig, err := signer.Sign(nil, th.Marshal(), crypto.Hash(0)) + if err != nil { + return nil, fmt.Errorf("Sign: %v", err) + } + + sigident := SigIdent{ + KeyHash: Hash(signer.Public().(ed25519.PublicKey)[:]), + Signature: &[SignatureSize]byte{}, + } + copy(sigident.Signature[:], sig) + return &SignedTreeHead{ + TreeHead: *th, + SigIdent: []*SigIdent{ + &sigident, + }, + }, nil +} + +// Verify verifies the tree head signature using the log's signature scheme +func (th *TreeHead) Verify(vk *[VerificationKeySize]byte, sig *[SignatureSize]byte) error { + if !ed25519.Verify(ed25519.PublicKey(vk[:]), th.Marshal(), sig[:]) { + return fmt.Errorf("invalid tree head signature") + } + return nil +} + +// Verify checks if a leaf is included in the log +func (p *InclusionProof) Verify(leaf *Leaf, th *TreeHead) error { // TODO + return nil +} + +// Verify checks if two tree heads are consistent +func (p *ConsistencyProof) Verify(oldTH, newTH *TreeHead) error { // TODO + return nil +} diff --git a/pkg/types/types_test.go b/pkg/types/types_test.go new file mode 100644 index 0000000..da89c59 --- /dev/null +++ b/pkg/types/types_test.go @@ -0,0 +1,58 @@ +package types + +import ( + "testing" +) + +func TestEndpointPath(t *testing.T) { + base, prefix, proto := "example.com", "log", "st/v0" + for _, table := range []struct { + endpoint Endpoint + want string + }{ + { + endpoint: EndpointAddLeaf, + want: "example.com/log/st/v0/add-leaf", + }, + { + endpoint: EndpointAddCosignature, + want: "example.com/log/st/v0/add-cosignature", + }, + { + endpoint: EndpointGetTreeHeadLatest, + want: "example.com/log/st/v0/get-tree-head-latest", + }, + { + endpoint: EndpointGetTreeHeadToSign, + want: "example.com/log/st/v0/get-tree-head-to-sign", + }, + { + endpoint: EndpointGetTreeHeadCosigned, + want: "example.com/log/st/v0/get-tree-head-cosigned", + }, + { + endpoint: EndpointGetConsistencyProof, + want: "example.com/log/st/v0/get-consistency-proof", + }, + { + endpoint: EndpointGetProofByHash, + want: "example.com/log/st/v0/get-proof-by-hash", + }, + { + endpoint: EndpointGetLeaves, + want: "example.com/log/st/v0/get-leaves", + }, + } { + if got, want := table.endpoint.Path(base+"/"+prefix+"/"+proto), table.want; got != want { + t.Errorf("got endpoint\n%s\n\tbut wanted\n%s\n\twith one component", got, want) + } + if got, want := table.endpoint.Path(base, prefix, proto), table.want; got != want { + t.Errorf("got endpoint\n%s\n\tbut wanted\n%s\n\tmultiple components", got, want) + } + } +} + +func TestTreeHeadSign(t *testing.T) {} +func TestTreeHeadVerify(t *testing.T) {} +func TestInclusionProofVerify(t *testing.T) {} +func TestConsistencyProofVerify(t *testing.T) {} diff --git a/pkg/types/util.go b/pkg/types/util.go new file mode 100644 index 0000000..3cd7dfa --- /dev/null +++ b/pkg/types/util.go @@ -0,0 +1,21 @@ +package types + +import ( + "crypto/sha256" +) + +const ( + LeafHashPrefix = 0x00 +) + +func Hash(buf []byte) *[HashSize]byte { + var ret [HashSize]byte + hash := sha256.New() + hash.Write(buf) + copy(ret[:], hash.Sum(nil)) + return &ret +} + +func HashLeaf(buf []byte) *[HashSize]byte { + return Hash(append([]byte{LeafHashPrefix}, buf...)) +} diff --git a/request.go b/request.go deleted file mode 100644 index 763d9ed..0000000 --- a/request.go +++ /dev/null @@ -1,81 +0,0 @@ -package stfe - -import ( - "fmt" - - "crypto/ed25519" - "net/http" - - "github.com/system-transparency/stfe/types" -) - -func (lp *LogParameters) parseAddEntryV1Request(r *http.Request) (*types.Leaf, error) { - var req types.LeafRequest - if err := req.UnmarshalASCII(r.Body); err != nil { - return nil, fmt.Errorf("UnmarshalASCII: %v", err) - } - - if pub, msg, sig := ed25519.PublicKey(req.VerificationKey[:]), req.Message.Marshal(), req.Signature[:]; !ed25519.Verify(pub, msg, sig) { - return nil, fmt.Errorf("Invalid signature") - } - // TODO: check shard hint - // TODO: check domain hint - return &types.Leaf{ - Message: req.Message, - SigIdent: types.SigIdent{ - Signature: req.Signature, - KeyHash: types.Hash(req.VerificationKey[:]), - }, - }, nil -} - -func (lp *LogParameters) parseAddCosignatureRequest(r *http.Request) (*types.CosignatureRequest, error) { - var req types.CosignatureRequest - if err := req.UnmarshalASCII(r.Body); err != nil { - return nil, fmt.Errorf("unpackOctetPost: %v", err) - } - if _, ok := lp.Witnesses[*req.KeyHash]; !ok { - return nil, fmt.Errorf("Unknown witness: %x", req.KeyHash) - } - return &req, nil -} - -func (lp *LogParameters) parseGetConsistencyProofRequest(r *http.Request) (*types.ConsistencyProofRequest, error) { - var req types.ConsistencyProofRequest - if err := req.UnmarshalASCII(r.Body); err != nil { - return nil, fmt.Errorf("UnmarshalASCII: %v", err) - } - if req.OldSize < 1 { - return nil, fmt.Errorf("OldSize(%d) must be larger than zero", req.OldSize) - } - if req.NewSize <= req.OldSize { - return nil, fmt.Errorf("NewSize(%d) must be larger than OldSize(%d)", req.NewSize, req.OldSize) - } - return &req, nil -} - -func (lp *LogParameters) parseGetProofByHashRequest(r *http.Request) (*types.InclusionProofRequest, error) { - var req types.InclusionProofRequest - if err := req.UnmarshalASCII(r.Body); err != nil { - return nil, fmt.Errorf("UnmarshalASCII: %v", err) - } - if req.TreeSize < 1 { - return nil, fmt.Errorf("TreeSize(%d) must be larger than zero", req.TreeSize) - } - return &req, nil -} - -func (lp *LogParameters) parseGetEntriesRequest(r *http.Request) (*types.LeavesRequest, error) { - var req types.LeavesRequest - if err := req.UnmarshalASCII(r.Body); err != nil { - return nil, fmt.Errorf("UnmarshalASCII: %v", err) - } - - if req.StartSize > req.EndSize { - return nil, fmt.Errorf("StartSize(%d) must be less than or equal to EndSize(%d)", req.StartSize, req.EndSize) - } - if req.EndSize-req.StartSize+1 > uint64(lp.MaxRange) { - req.EndSize = req.StartSize + uint64(lp.MaxRange) - 1 - } - return &req, nil -} diff --git a/request_test.go b/request_test.go deleted file mode 100644 index 102c56f..0000000 --- a/request_test.go +++ /dev/null @@ -1,318 +0,0 @@ -package stfe - -import ( - "bytes" - //"fmt" - "reflect" - "testing" - //"testing/iotest" - - "net/http" - - "github.com/system-transparency/stfe/testdata" - "github.com/system-transparency/stfe/types" -) - -func TestParseAddEntryV1Request(t *testing.T) { - lp := newLogParameters(t, nil) - for _, table := range []struct { - description string - breq *bytes.Buffer - wantErr bool - }{ - { - description: "invalid: nothing to unpack", - breq: bytes.NewBuffer(nil), - wantErr: true, - }, - { - description: "invalid: not a signed checksum entry", - breq: testdata.AddCosignatureBuffer(t, testdata.DefaultSth(t, testdata.Ed25519VkLog), &testdata.Ed25519SkWitness, &testdata.Ed25519VkWitness), - wantErr: true, - }, - { - description: "invalid: untrusted submitter", // only testdata.Ed25519VkSubmitter is registered by default in newLogParameters() - - breq: testdata.AddSignedChecksumBuffer(t, testdata.Ed25519SkSubmitter2, testdata.Ed25519VkSubmitter2), - wantErr: true, - }, - { - description: "invalid: signature does not cover message", - - breq: testdata.AddSignedChecksumBuffer(t, testdata.Ed25519SkSubmitter2, testdata.Ed25519VkSubmitter), - wantErr: true, - }, - { - description: "valid", - breq: testdata.AddSignedChecksumBuffer(t, testdata.Ed25519SkSubmitter, testdata.Ed25519VkSubmitter), - }, // TODO: add test case that disables submitter policy (i.e., unregistered namespaces are accepted) - } { - url := EndpointAddEntry.Path("http://example.com", lp.Prefix) - req, err := http.NewRequest("POST", url, table.breq) - if err != nil { - t.Fatalf("failed creating http request: %v", err) - } - req.Header.Set("Content-Type", "application/octet-stream") - - _, err = lp.parseAddEntryV1Request(req) - if got, want := err != nil, table.wantErr; got != want { - t.Errorf("got errror %v but wanted %v in test %q: %v", got, want, table.description, err) - } - } -} - -func TestParseAddCosignatureV1Request(t *testing.T) { - lp := newLogParameters(t, nil) - for _, table := range []struct { - description string - breq *bytes.Buffer - wantErr bool - }{ - { - description: "invalid: nothing to unpack", - breq: bytes.NewBuffer(nil), - wantErr: true, - }, - { - description: "invalid: not a cosigned sth", - breq: testdata.AddSignedChecksumBuffer(t, testdata.Ed25519SkSubmitter, testdata.Ed25519VkSubmitter), - wantErr: true, - }, - { - description: "invalid: no cosignature", - breq: testdata.AddCosignatureBuffer(t, testdata.DefaultSth(t, testdata.Ed25519VkLog), &testdata.Ed25519SkWitness, nil), - wantErr: true, - }, - { - description: "invalid: untrusted witness", // only testdata.Ed25519VkWitness is registered by default in newLogParameters() - breq: testdata.AddCosignatureBuffer(t, testdata.DefaultSth(t, testdata.Ed25519VkLog), &testdata.Ed25519SkWitness2, &testdata.Ed25519VkWitness2), - wantErr: true, - }, - { - description: "invalid: signature does not cover message", - breq: testdata.AddCosignatureBuffer(t, testdata.DefaultSth(t, testdata.Ed25519VkLog), &testdata.Ed25519SkWitness2, &testdata.Ed25519VkWitness), - wantErr: true, - }, - { - description: "valid", - breq: testdata.AddCosignatureBuffer(t, testdata.DefaultSth(t, testdata.Ed25519VkLog), &testdata.Ed25519SkWitness, &testdata.Ed25519VkWitness), - }, // TODO: add test case that disables witness policy (i.e., unregistered namespaces are accepted) - } { - url := EndpointAddCosignature.Path("http://example.com", lp.Prefix) - req, err := http.NewRequest("POST", url, table.breq) - if err != nil { - t.Fatalf("failed creating http request: %v", err) - } - req.Header.Set("Content-Type", "application/octet-stream") - - _, err = lp.parseAddCosignatureV1Request(req) - if got, want := err != nil, table.wantErr; got != want { - t.Errorf("got errror %v but wanted %v in test %q: %v", got, want, table.description, err) - } - } -} - -func TestNewGetConsistencyProofRequest(t *testing.T) { - lp := newLogParameters(t, nil) - for _, table := range []struct { - description string - req *types.GetConsistencyProofV1 - wantErr bool - }{ - { - description: "invalid: nothing to unpack", - req: nil, - wantErr: true, - }, - { - description: "invalid: first must be larger than zero", - req: &types.GetConsistencyProofV1{First: 0, Second: 0}, - wantErr: true, - }, - { - description: "invalid: second must be larger than first", - req: &types.GetConsistencyProofV1{First: 2, Second: 1}, - wantErr: true, - }, - { - description: "valid", - req: &types.GetConsistencyProofV1{First: 1, Second: 2}, - }, - } { - var buf *bytes.Buffer - if table.req == nil { - buf = bytes.NewBuffer(nil) - } else { - buf = bytes.NewBuffer(marshal(t, *table.req)) - } - - url := EndpointGetConsistencyProof.Path("http://example.com", lp.Prefix) - req, err := http.NewRequest("POST", url, buf) - if err != nil { - t.Fatalf("failed creating http request: %v", err) - } - req.Header.Set("Content-Type", "application/octet-stream") - - _, err = lp.parseGetConsistencyProofV1Request(req) - if got, want := err != nil, table.wantErr; got != want { - t.Errorf("got errror %v but wanted %v in test %q: %v", got, want, table.description, err) - } - } -} - -func TestNewGetProofByHashRequest(t *testing.T) { - lp := newLogParameters(t, nil) - for _, table := range []struct { - description string - req *types.GetProofByHashV1 - wantErr bool - }{ - { - description: "invalid: nothing to unpack", - req: nil, - wantErr: true, - }, - { - description: "invalid: no entry in an empty tree", - req: &types.GetProofByHashV1{TreeSize: 0, Hash: testdata.LeafHash}, - wantErr: true, - }, - { - description: "valid", - req: &types.GetProofByHashV1{TreeSize: 1, Hash: testdata.LeafHash}, - }, - } { - var buf *bytes.Buffer - if table.req == nil { - buf = bytes.NewBuffer(nil) - } else { - buf = bytes.NewBuffer(marshal(t, *table.req)) - } - - url := EndpointGetProofByHash.Path("http://example.com", lp.Prefix) - req, err := http.NewRequest("POST", url, buf) - if err != nil { - t.Fatalf("failed creating http request: %v", err) - } - req.Header.Set("Content-Type", "application/octet-stream") - - _, err = lp.parseGetProofByHashV1Request(req) - if got, want := err != nil, table.wantErr; got != want { - t.Errorf("got errror %v but wanted %v in test %q: %v", got, want, table.description, err) - } - } -} - -func TestParseGetEntriesV1Request(t *testing.T) { - lp := newLogParameters(t, nil) - for _, table := range []struct { - description string - req *types.GetEntriesV1 - wantErr bool - wantReq *types.GetEntriesV1 - }{ - { - description: "invalid: nothing to unpack", - req: nil, - wantErr: true, - }, - { - description: "invalid: start must be larger than end", - req: &types.GetEntriesV1{Start: 1, End: 0}, - wantErr: true, - }, - { - description: "valid: want truncated range", - req: &types.GetEntriesV1{Start: 0, End: uint64(testdata.MaxRange)}, - wantReq: &types.GetEntriesV1{Start: 0, End: uint64(testdata.MaxRange) - 1}, - }, - { - description: "valid", - req: &types.GetEntriesV1{Start: 0, End: 0}, - wantReq: &types.GetEntriesV1{Start: 0, End: 0}, - }, - } { - var buf *bytes.Buffer - if table.req == nil { - buf = bytes.NewBuffer(nil) - } else { - buf = bytes.NewBuffer(marshal(t, *table.req)) - } - - url := EndpointGetEntries.Path("http://example.com", lp.Prefix) - req, err := http.NewRequest("POST", url, buf) - if err != nil { - t.Fatalf("failed creating http request: %v", err) - } - req.Header.Set("Content-Type", "application/octet-stream") - - output, err := lp.parseGetEntriesV1Request(req) - if got, want := err != nil, table.wantErr; got != want { - t.Errorf("got errror %v but wanted %v in test %q: %v", got, want, table.description, err) - } - if err != nil { - continue - } - if got, want := output, table.wantReq; !reflect.DeepEqual(got, want) { - t.Errorf("got request\n%v\n\tbut wanted\n%v\n\t in test %q", got, want, table.description) - } - } -} - -func TestUnpackOctetPost(t *testing.T) { - for _, table := range []struct { - description string - req *http.Request - out interface{} - wantErr bool - }{ - //{ - // description: "invalid: cannot read request body", - // req: func() *http.Request { - // req, err := http.NewRequest(http.MethodPost, "", iotest.ErrReader(fmt.Errorf("bad reader"))) - // if err != nil { - // t.Fatalf("must make new http request: %v", err) - // } - // return req - // }(), - // out: &types.StItem{}, - // wantErr: true, - //}, // testcase requires Go 1.16 - { - description: "invalid: cannot unmarshal", - req: func() *http.Request { - req, err := http.NewRequest(http.MethodPost, "", bytes.NewBuffer(nil)) - if err != nil { - t.Fatalf("must make new http request: %v", err) - } - return req - }(), - out: &types.StItem{}, - wantErr: true, - }, - { - description: "valid", - req: func() *http.Request { - req, err := http.NewRequest(http.MethodPost, "", bytes.NewBuffer([]byte{0})) - if err != nil { - t.Fatalf("must make new http request: %v", err) - } - return req - }(), - out: &struct{ SomeUint8 uint8 }{}, - }, - } { - err := unpackOctetPost(table.req, table.out) - if got, want := err != nil, table.wantErr; got != want { - t.Errorf("got error %v but wanted %v in test %q", got, want, table.description) - } - } -} - -func marshal(t *testing.T, out interface{}) []byte { - b, err := types.Marshal(out) - if err != nil { - t.Fatalf("must marshal: %v", err) - } - return b -} diff --git a/server/.gitignore b/server/.gitignore deleted file mode 100644 index 254defd..0000000 --- a/server/.gitignore +++ /dev/null @@ -1 +0,0 @@ -server diff --git a/server/README.md b/server/README.md deleted file mode 100644 index 71bb3ac..0000000 --- a/server/README.md +++ /dev/null @@ -1,60 +0,0 @@ -# Run Trillian + STFE locally -Trillian uses a database. So, we will need to set that up. It is documented -[here](https://github.com/google/trillian#mysql-setup), and how to check that it -is setup properly -[here](https://github.com/google/certificate-transparency-go/blob/master/trillian/docs/ManualDeployment.md#data-storage). - -Other than the database we need the Trillian log signer, Trillian log server, -and STFE server. -``` -$ go install github.com/google/trillian/cmd/trillian_log_signer -$ go install github.com/google/trillian/cmd/trillian_log_server -$ go install -``` - -Start Trillian log signer: -``` -trillian_log_signer --logtostderr -v 9 --force_master --rpc_endpoint=localhost:6961 --http_endpoint=localhost:6964 --num_sequencers 1 --sequencer_interval 100ms --batch_size 100 -``` - -Start Trillian log server: -``` -trillian_log_server --logtostderr -v 9 --rpc_endpoint=localhost:6962 --http_endpoint=localhost:6963 -``` - -As described in more detail -[here](https://github.com/google/certificate-transparency-go/blob/master/trillian/docs/ManualDeployment.md#trillian-services), -we need to provision a Merkle tree once: -``` -$ go install github.com/google/trillian/cmd/createtree -$ createtree --admin_server localhost:6962 - -``` - -Hang on to ``. Our STFE server will use it when talking to the -Trillian log server to specify which Merkle tree we are working against. - -(If you take a look in the `Trees` table you will see that the tree has been -provisioned.) - -We will also need a public key-pair and log identifier for the STFE server. -``` -$ go install github.com/system-transparency/stfe/types/cmd/new-namespace -sk: -vk: -ed25519_v1: -``` - -The log's identifier is `` and contains the public verification key -``. The log's corresponding secret signing key is ``. - -Start STFE server: -``` -$ ./server --logtostderr -v 9 --http_endpoint localhost:6965 --log_rpc_server localhost:6962 --trillian_id --key -``` - -If the log is responsive on, e.g., `GET http://localhost:6965/st/v1/get-latest-sth` you -may want to try running -`github.com/system-transparency/stfe/client/cmd/example.sh`. You need to -configure the log's id though for verification to work (flag `log_id`, which -should be set to the `` output above). diff --git a/server/main.go b/server/main.go deleted file mode 100644 index 1fecb43..0000000 --- a/server/main.go +++ /dev/null @@ -1,165 +0,0 @@ -// Package main provides an STFE server binary -package main - -import ( - "context" - "crypto" - "crypto/ed25519" - "encoding/hex" - "flag" - "fmt" - "net/http" - "os" - "os/signal" - "strings" - "sync" - "syscall" - "time" - - "github.com/golang/glog" - "github.com/google/trillian" - "github.com/prometheus/client_golang/prometheus/promhttp" - "github.com/system-transparency/stfe" - "github.com/system-transparency/stfe/types" - "google.golang.org/grpc" -) - -var ( - httpEndpoint = flag.String("http_endpoint", "localhost:6965", "host:port specification of where stfe serves clients") - rpcBackend = flag.String("log_rpc_server", "localhost:6962", "host:port specification of where Trillian serves clients") - prefix = flag.String("prefix", "st/v0", "a prefix that proceeds each endpoint path") - trillianID = flag.Int64("trillian_id", 0, "log identifier in the Trillian database") - deadline = flag.Duration("deadline", time.Second*10, "deadline for backend requests") - key = flag.String("key", "", "hex-encoded Ed25519 signing key") - witnesses = flag.String("witnesses", "", "comma-separated list of trusted witness verification keys in hex") - maxRange = flag.Int64("max_range", 10, "maximum number of entries that can be retrived in a single request") - interval = flag.Duration("interval", time.Second*30, "interval used to rotate the log's cosigned STH") -) - -func main() { - flag.Parse() - defer glog.Flush() - - // wait for clean-up before exit - var wg sync.WaitGroup - defer wg.Wait() - ctx, cancel := context.WithCancel(context.Background()) - defer cancel() - - glog.V(3).Infof("configuring stfe instance...") - instance, err := setupInstanceFromFlags() - if err != nil { - glog.Errorf("setupInstance: %v", err) - return - } - - glog.V(3).Infof("spawning SthSource") - go func() { - wg.Add(1) - defer wg.Done() - instance.SthSource.Run(ctx) - glog.Errorf("SthSource shutdown") - cancel() // must have SthSource running - }() - - glog.V(3).Infof("spawning await") - server := http.Server{Addr: *httpEndpoint} - go await(ctx, func() { - wg.Add(1) - defer wg.Done() - ctxInner, _ := context.WithTimeout(ctx, time.Second*60) - glog.Infof("Shutting down HTTP server...") - server.Shutdown(ctxInner) - glog.V(3).Infof("HTTP server shutdown") - glog.Infof("Shutting down spawned go routines...") - cancel() - }) - - glog.Infof("Serving on %v/%v", *httpEndpoint, *prefix) - if err = server.ListenAndServe(); err != http.ErrServerClosed { - glog.Errorf("ListenAndServe: %v", err) - } -} - -// SetupInstance sets up a new STFE instance from flags -func setupInstanceFromFlags() (*stfe.Instance, error) { - // Trillian gRPC connection - dialOpts := []grpc.DialOption{grpc.WithInsecure(), grpc.WithBlock(), grpc.WithTimeout(*deadline)} - conn, err := grpc.Dial(*rpcBackend, dialOpts...) - if err != nil { - return nil, fmt.Errorf("Dial: %v", err) - } - client := trillian.NewTrillianLogClient(conn) - // HTTP multiplexer - mux := http.NewServeMux() - http.Handle("/", mux) - // Prometheus metrics - glog.V(3).Infof("Adding prometheus handler on path: /metrics") - http.Handle("/metrics", promhttp.Handler()) - // Trusted witnesses - witnesses, err := newWitnessMap(*witnesses) - if err != nil { - return nil, fmt.Errorf("newWitnessMap: %v", err) - } - // Secret signing key - sk, err := hex.DecodeString(*key) - if err != nil { - return nil, fmt.Errorf("sk: DecodeString: %v", err) - } - // Setup log parameters - lp := &stfe.LogParameters{ - LogId: hex.EncodeToString([]byte(ed25519.PrivateKey(sk).Public().(ed25519.PublicKey))), - TreeId: *trillianID, - Prefix: *prefix, - MaxRange: *maxRange, - Deadline: *deadline, - Interval: *interval, - HashType: crypto.SHA256, - Signer: ed25519.PrivateKey(sk), - Witnesses: witnesses, - } - // Setup STH source - source, err := stfe.NewActiveSthSource(client, lp) - if err != nil { - return nil, fmt.Errorf("NewActiveSthSource: %v", err) - } - // Setup log instance - i := &stfe.Instance{client, lp, source} - for _, handler := range i.Handlers() { - glog.V(3).Infof("adding handler: %s", handler.Path()) - mux.Handle(handler.Path(), handler) - } - return i, nil -} - -// newWitnessMap creates a new map of trusted witnesses -func newWitnessMap(witnesses string) (map[[types.HashSize]byte][types.VerificationKeySize]byte, error) { - w := make(map[[types.HashSize]byte][types.VerificationKeySize]byte) - if len(witnesses) > 0 { - for _, witness := range strings.Split(witnesses, ",") { - b, err := hex.DecodeString(witness) - if err != nil { - return nil, fmt.Errorf("DecodeString: %v", err) - } - - var vk [types.VerificationKeySize]byte - if n := copy(vk[:], b); n != types.VerificationKeySize { - return nil, fmt.Errorf("Invalid verification key size: %v", n) - } - w[*types.Hash(vk[:])] = vk - } - } - return w, nil -} - -// await waits for a shutdown signal and then runs a clean-up function -func await(ctx context.Context, done func()) { - sigs := make(chan os.Signal, 1) - signal.Notify(sigs, syscall.SIGINT, syscall.SIGTERM) - select { - case <-sigs: - case <-ctx.Done(): - } - glog.V(3).Info("received shutdown signal") - done() -} diff --git a/state/state_manager.go b/state/state_manager.go deleted file mode 100644 index 3199e61..0000000 --- a/state/state_manager.go +++ /dev/null @@ -1,154 +0,0 @@ -package stfe - -import ( - "context" - "crypto" - "fmt" - "reflect" - "sync" - "time" - - "github.com/golang/glog" - "github.com/google/certificate-transparency-go/schedule" - "github.com/system-transparency/stfe/trillian" - "github.com/system-transparency/stfe/types" -) - -// StateManager coordinates access to the log's tree heads and (co)signatures -type StateManager interface { - Latest(context.Context) (*types.SignedTreeHead, error) - ToSign(context.Context) (*types.SignedTreeHead, error) - Cosigned(context.Context) (*types.SignedTreeHead, error) - AddCosignature(context.Context, *[types.VerificationKeySize]byte, *[types.SignatureSize]byte) error - Run(context.Context) -} - -// StateManagerSingle implements the StateManager interface. It is assumed that -// the log server is running on a single-instance machine. So, no coordination. -type StateManagerSingle struct { - client trillian.Client - signer crypto.Signer - interval time.Duration - deadline time.Duration - sync.RWMutex - - // cosigned is the current cosigned tree head that is being served - cosigned types.SignedTreeHead - - // tosign is the current tree head that is being cosigned by witnesses - tosign types.SignedTreeHead - - // cosignature keeps track of all cosignatures for the tosign tree head - cosignature map[[types.HashSize]byte]*types.SigIdent -} - -func NewStateManagerSingle(client trillian.Client, signer crypto.Signer, interval, deadline time.Duration) (*StateManagerSingle, error) { - sm := &StateManagerSingle{ - client: client, - signer: signer, - interval: interval, - deadline: deadline, - } - - ctx, _ := context.WithTimeout(context.Background(), sm.deadline) - sth, err := sm.Latest(ctx) - if err != nil { - return nil, fmt.Errorf("Latest: %v", err) - } - - sm.cosigned = *sth - sm.tosign = *sth - sm.cosignature = map[[types.HashSize]byte]*types.SigIdent{ - *sth.SigIdent[0].KeyHash: sth.SigIdent[0], // log signature - } - return sm, nil -} - -func (sm *StateManagerSingle) Run(ctx context.Context) { - schedule.Every(ctx, sm.interval, func(ctx context.Context) { - ictx, _ := context.WithTimeout(ctx, sm.deadline) - nextTreeHead, err := sm.Latest(ictx) - if err != nil { - glog.Warningf("rotate failed: Latest: %v", err) - return - } - - sm.Lock() - defer sm.Unlock() - sm.rotate(nextTreeHead) - }) -} - -func (sm *StateManagerSingle) Latest(ctx context.Context) (*types.SignedTreeHead, error) { - th, err := sm.client.GetTreeHead(ctx) - if err != nil { - return nil, fmt.Errorf("LatestTreeHead: %v", err) - } - sth, err := th.Sign(sm.signer) - if err != nil { - return nil, fmt.Errorf("sign: %v", err) - } - return sth, nil -} - -func (sm *StateManagerSingle) ToSign(_ context.Context) (*types.SignedTreeHead, error) { - sm.RLock() - defer sm.RUnlock() - return &sm.tosign, nil -} - -func (sm *StateManagerSingle) Cosigned(_ context.Context) (*types.SignedTreeHead, error) { - sm.RLock() - defer sm.RUnlock() - return &sm.cosigned, nil -} - -func (sm *StateManagerSingle) AddCosignature(_ context.Context, vk *[types.VerificationKeySize]byte, sig *[types.SignatureSize]byte) error { - sm.Lock() - defer sm.Unlock() - - if err := sm.tosign.TreeHead.Verify(vk, sig); err != nil { - return fmt.Errorf("Verify: %v", err) - } - witness := types.Hash(vk[:]) - if _, ok := sm.cosignature[*witness]; ok { - return fmt.Errorf("signature-signer pair is a duplicate") - } - sm.cosignature[*witness] = &types.SigIdent{ - Signature: sig, - KeyHash: witness, - } - - glog.V(3).Infof("accepted new cosignature from witness: %x", *witness) - return nil -} - -// rotate rotates the log's cosigned and stable STH. The caller must aquire the -// source's read-write lock if there are concurrent reads and/or writes. -func (sm *StateManagerSingle) rotate(next *types.SignedTreeHead) { - if reflect.DeepEqual(sm.cosigned.TreeHead, sm.tosign.TreeHead) { - // cosigned and tosign are the same. So, we need to merge all - // cosignatures that we already had with the new collected ones. - for _, sigident := range sm.cosigned.SigIdent { - if _, ok := sm.cosignature[*sigident.KeyHash]; !ok { - sm.cosignature[*sigident.KeyHash] = sigident - } - } - glog.V(3).Infof("cosigned tree head repeated, merged signatures") - } - var cosignatures []*types.SigIdent - for _, sigident := range sm.cosignature { - cosignatures = append(cosignatures, sigident) - } - - // Update cosigned tree head - sm.cosigned.TreeHead = sm.tosign.TreeHead - sm.cosigned.SigIdent = cosignatures - - // Update to-sign tree head - sm.tosign = *next - sm.cosignature = map[[types.HashSize]byte]*types.SigIdent{ - *next.SigIdent[0].KeyHash: next.SigIdent[0], // log signature - } - glog.V(3).Infof("rotated tree heads") -} diff --git a/state/state_manager_test.go b/state/state_manager_test.go deleted file mode 100644 index 348074c..0000000 --- a/state/state_manager_test.go +++ /dev/null @@ -1,393 +0,0 @@ -package stfe - -import ( - "bytes" - "context" - "crypto" - "crypto/ed25519" - "crypto/rand" - "fmt" - "reflect" - "testing" - "time" - - "github.com/golang/mock/gomock" - "github.com/system-transparency/stfe/mocks" - "github.com/system-transparency/stfe/types" -) - -var ( - testSig = &[types.SignatureSize]byte{} - testPub = &[types.VerificationKeySize]byte{} - testTH = &types.TreeHead{ - Timestamp: 0, - TreeSize: 0, - RootHash: types.Hash(nil), - } - testSigIdent = &types.SigIdent{ - Signature: testSig, - KeyHash: types.Hash(testPub[:]), - } - testSTH = &types.SignedTreeHead{ - TreeHead: *testTH, - SigIdent: []*types.SigIdent{testSigIdent}, - } - testSignerOK = &mocks.TestSigner{testPub, testSig, nil} - testSignerErr = &mocks.TestSigner{testPub, testSig, fmt.Errorf("something went wrong")} -) - -func TestNewStateManagerSingle(t *testing.T) { - for _, table := range []struct { - description string - signer crypto.Signer - rsp *types.TreeHead - err error - wantErr bool - wantSth *types.SignedTreeHead - }{ - { - description: "invalid: backend failure", - signer: testSignerOK, - err: fmt.Errorf("something went wrong"), - wantErr: true, - }, - { - description: "valid", - signer: testSignerOK, - rsp: testTH, - wantSth: testSTH, - }, - } { - // Run deferred functions at the end of each iteration - func() { - ctrl := gomock.NewController(t) - defer ctrl.Finish() - client := mocks.NewMockClient(ctrl) - client.EXPECT().GetTreeHead(gomock.Any()).Return(table.rsp, table.err) - - sm, err := NewStateManagerSingle(client, table.signer, time.Duration(0), time.Duration(0)) - if got, want := err != nil, table.wantErr; got != want { - t.Errorf("got error %v but wanted %v in test %q: %v", got, want, table.description, err) - } - if err != nil { - return - } - if got, want := &sm.cosigned, table.wantSth; !reflect.DeepEqual(got, want) { - t.Errorf("got cosigned tree head\n\t%v\nbut wanted\n\t%v\nin test %q", got, want, table.description) - } - if got, want := &sm.tosign, table.wantSth; !reflect.DeepEqual(got, want) { - t.Errorf("got tosign tree head\n\t%v\nbut wanted\n\t%v\nin test %q", got, want, table.description) - } - // we only have log signature on startup - if got, want := len(sm.cosignature), 1; got != want { - t.Errorf("got %d cosignatures but wanted %d in test %q", got, want, table.description) - } - }() - } -} - -func TestLatest(t *testing.T) { - for _, table := range []struct { - description string - signer crypto.Signer - rsp *types.TreeHead - err error - wantErr bool - wantSth *types.SignedTreeHead - }{ - { - description: "invalid: backend failure", - signer: testSignerOK, - err: fmt.Errorf("something went wrong"), - wantErr: true, - }, - { - description: "invalid: signature failure", - rsp: testTH, - signer: testSignerErr, - wantErr: true, - }, - { - description: "valid", - signer: testSignerOK, - rsp: testTH, - wantSth: testSTH, - }, - } { - // Run deferred functions at the end of each iteration - func() { - ctrl := gomock.NewController(t) - defer ctrl.Finish() - client := mocks.NewMockClient(ctrl) - client.EXPECT().GetTreeHead(gomock.Any()).Return(table.rsp, table.err) - sm := StateManagerSingle{ - client: client, - signer: table.signer, - } - - sth, err := sm.Latest(context.Background()) - if got, want := err != nil, table.wantErr; got != want { - t.Errorf("got error %v but wanted %v in test %q: %v", got, want, table.description, err) - } - if err != nil { - return - } - if got, want := sth, table.wantSth; !reflect.DeepEqual(got, want) { - t.Errorf("got signed tree head\n\t%v\nbut wanted\n\t%v\nin test %q", got, want, table.description) - } - }() - } -} - -func TestToSign(t *testing.T) { - description := "valid" - sm := StateManagerSingle{ - tosign: *testSTH, - } - sth, err := sm.ToSign(context.Background()) - if err != nil { - t.Errorf("ToSign should not fail with error: %v", err) - return - } - if got, want := sth, testSTH; !reflect.DeepEqual(got, want) { - t.Errorf("got signed tree head\n\t%v\nbut wanted\n\t%v\nin test %q", got, want, description) - } -} - -func TestCosigned(t *testing.T) { - description := "valid" - sm := StateManagerSingle{ - cosigned: *testSTH, - } - sth, err := sm.Cosigned(context.Background()) - if err != nil { - t.Errorf("Cosigned should not fail with error: %v", err) - return - } - if got, want := sth, testSTH; !reflect.DeepEqual(got, want) { - t.Errorf("got signed tree head\n\t%v\nbut wanted\n\t%v\nin test %q", got, want, description) - } -} - -func TestAddCosignature(t *testing.T) { - vk, sk, err := ed25519.GenerateKey(rand.Reader) - if err != nil { - t.Fatalf("GenerateKey: %v", err) - } - if bytes.Equal(vk[:], testPub[:]) { - t.Fatalf("Sampled same key as testPub, aborting...") - } - var vkArray [types.VerificationKeySize]byte - copy(vkArray[:], vk[:]) - - for _, table := range []struct { - description string - signer crypto.Signer - vk *[types.VerificationKeySize]byte - th *types.TreeHead - wantErr bool - }{ - { - description: "invalid: signature error", - signer: sk, - vk: testPub, // wrong key for message - th: testTH, - wantErr: true, - }, - { - description: "valid", - signer: sk, - vk: &vkArray, - th: testTH, - }, - } { - sth, _ := table.th.Sign(testSignerOK) - logKeyHash := sth.SigIdent[0].KeyHash - logSigIdent := sth.SigIdent[0] - sm := &StateManagerSingle{ - signer: testSignerOK, - cosigned: *sth, - tosign: *sth, - cosignature: map[[types.HashSize]byte]*types.SigIdent{ - *logKeyHash: logSigIdent, - }, - } - - // Prepare witness signature - sth, err := table.th.Sign(table.signer) - if err != nil { - t.Fatalf("Sign: %v", err) - } - witnessKeyHash := sth.SigIdent[0].KeyHash - witnessSigIdent := sth.SigIdent[0] - - // Add witness signature - err = sm.AddCosignature(context.Background(), table.vk, witnessSigIdent.Signature) - if got, want := err != nil, table.wantErr; got != want { - t.Errorf("got error %v but wanted %v in test %q: %v", got, want, table.description, err) - } - if err != nil { - continue - } - - // We should have two signatures (log + witness) - if got, want := len(sm.cosignature), 2; got != want { - t.Errorf("got %d cosignatures but wanted %v in test %q", got, want, table.description) - continue - } - // check that log signature is there - sigident, ok := sm.cosignature[*logKeyHash] - if !ok { - t.Errorf("log signature is missing") - continue - } - if got, want := sigident, logSigIdent; !reflect.DeepEqual(got, want) { - t.Errorf("got log sigident\n\t%v\nbut wanted\n\t%v\nin test %q", got, want, table.description) - } - // check that witness signature is there - sigident, ok = sm.cosignature[*witnessKeyHash] - if !ok { - t.Errorf("witness signature is missing") - continue - } - if got, want := sigident, witnessSigIdent; !reflect.DeepEqual(got, want) { - t.Errorf("got witness sigident\n\t%v\nbut wanted\n\t%v\nin test %q", got, want, table.description) - continue - } - - // Adding a duplicate signature should give an error - if err := sm.AddCosignature(context.Background(), table.vk, witnessSigIdent.Signature); err == nil { - t.Errorf("duplicate witness signature accepted as valid") - } - } -} - -func TestRotate(t *testing.T) { - log := testSigIdent - wit1 := &types.SigIdent{ - Signature: testSig, - KeyHash: types.Hash([]byte("wit1 key")), - } - wit2 := &types.SigIdent{ - Signature: testSig, - KeyHash: types.Hash([]byte("wit2 key")), - } - th0 := testTH - th1 := &types.TreeHead{ - Timestamp: 1, - TreeSize: 1, - RootHash: types.Hash([]byte("1")), - } - th2 := &types.TreeHead{ - Timestamp: 2, - TreeSize: 2, - RootHash: types.Hash([]byte("2")), - } - - for _, table := range []struct { - description string - before, after *StateManagerSingle - next *types.SignedTreeHead - }{ - { - description: "tosign tree head repated, but got one new witnes signature", - before: &StateManagerSingle{ - cosigned: types.SignedTreeHead{ - TreeHead: *th0, - SigIdent: []*types.SigIdent{log, wit1}, - }, - tosign: types.SignedTreeHead{ - TreeHead: *th0, - SigIdent: []*types.SigIdent{log}, - }, - cosignature: map[[types.HashSize]byte]*types.SigIdent{ - *log.KeyHash: log, - *wit2.KeyHash: wit2, // the new witness signature - }, - }, - next: &types.SignedTreeHead{ - TreeHead: *th1, - SigIdent: []*types.SigIdent{log}, - }, - after: &StateManagerSingle{ - cosigned: types.SignedTreeHead{ - TreeHead: *th0, - SigIdent: []*types.SigIdent{log, wit1, wit2}, - }, - tosign: types.SignedTreeHead{ - TreeHead: *th1, - SigIdent: []*types.SigIdent{log}, - }, - cosignature: map[[types.HashSize]byte]*types.SigIdent{ - *log.KeyHash: log, // after rotate we always have log sig - }, - }, - }, - { - description: "tosign tree head did not repeat, it got one witness signature", - before: &StateManagerSingle{ - cosigned: types.SignedTreeHead{ - TreeHead: *th0, - SigIdent: []*types.SigIdent{log, wit1}, - }, - tosign: types.SignedTreeHead{ - TreeHead: *th1, - SigIdent: []*types.SigIdent{log}, - }, - cosignature: map[[types.HashSize]byte]*types.SigIdent{ - *log.KeyHash: log, - *wit2.KeyHash: wit2, // the only witness that signed tosign - }, - }, - next: &types.SignedTreeHead{ - TreeHead: *th2, - SigIdent: []*types.SigIdent{log}, - }, - after: &StateManagerSingle{ - cosigned: types.SignedTreeHead{ - TreeHead: *th1, - SigIdent: []*types.SigIdent{log, wit2}, - }, - tosign: types.SignedTreeHead{ - TreeHead: *th2, - SigIdent: []*types.SigIdent{log}, - }, - cosignature: map[[types.HashSize]byte]*types.SigIdent{ - *log.KeyHash: log, // after rotate we always have log sig - }, - }, - }, - } { - table.before.rotate(table.next) - if got, want := table.before.cosigned.TreeHead, table.after.cosigned.TreeHead; !reflect.DeepEqual(got, want) { - t.Errorf("got cosigned tree head\n\t%v\nbut wanted\n\t%v\nin test %q", got, want, table.description) - } - checkWitnessList(t, table.description, table.before.cosigned.SigIdent, table.after.cosigned.SigIdent) - if got, want := table.before.tosign.TreeHead, table.after.tosign.TreeHead; !reflect.DeepEqual(got, want) { - t.Errorf("got tosign tree head\n\t%v\nbut wanted\n\t%v\nin test %q", got, want, table.description) - } - checkWitnessList(t, table.description, table.before.tosign.SigIdent, table.after.tosign.SigIdent) - if got, want := table.before.cosignature, table.after.cosignature; !reflect.DeepEqual(got, want) { - t.Errorf("got cosignature map\n\t%v\nbut wanted\n\t%v\nin test %q", got, want, table.description) - } - } -} - -func checkWitnessList(t *testing.T, description string, got, want []*types.SigIdent) { - t.Helper() - for _, si := range got { - found := false - for _, sj := range want { - if reflect.DeepEqual(si, sj) { - found = true - break - } - } - if !found { - t.Errorf("got unexpected signature-signer pair with key hash in test %q: %x", description, si.KeyHash[:]) - } - } - if len(got) != len(want) { - t.Errorf("got %d signature-signer pairs but wanted %d in test %q", len(got), len(want), description) - } -} diff --git a/sth.go b/sth.go deleted file mode 100644 index 1399241..0000000 --- a/sth.go +++ /dev/null @@ -1,143 +0,0 @@ -package stfe - -import ( - "context" - "crypto/ed25519" - "fmt" - "reflect" - "sync" - - "github.com/golang/glog" - "github.com/google/certificate-transparency-go/schedule" - "github.com/google/trillian" - ttypes "github.com/google/trillian/types" - "github.com/system-transparency/stfe/types" -) - -// SthSource provides access to the log's (co)signed tree heads -type SthSource interface { - Latest(context.Context) (*types.SignedTreeHead, error) - Stable(context.Context) (*types.SignedTreeHead, error) - Cosigned(context.Context) (*types.SignedTreeHead, error) - AddCosignature(context.Context, ed25519.PublicKey, *[types.SignatureSize]byte) error - Run(context.Context) -} - -// ActiveSthSource implements the SthSource interface for an STFE instance that -// accepts new logging requests, i.e., the log is running in read+write mode. -type ActiveSthSource struct { - client trillian.TrillianLogClient - logParameters *LogParameters - sync.RWMutex - - // cosigned is the current cosigned tree head that is served - cosigned types.SignedTreeHead - - // tosign is the current tree head that is being cosigned - tosign types.SignedTreeHead - - // cosignature keeps track of all collected cosignatures for tosign - cosignature map[[types.HashSize]byte]*types.SigIdent -} - -func NewActiveSthSource(cli trillian.TrillianLogClient, lp *LogParameters) (*ActiveSthSource, error) { - s := ActiveSthSource{ - client: cli, - logParameters: lp, - } - - ctx, _ := context.WithTimeout(context.Background(), lp.Deadline) - sth, err := s.Latest(ctx) - if err != nil { - return nil, fmt.Errorf("Latest: %v", err) - } - - s.cosigned = *sth - s.tosign = *sth - s.cosignature = make(map[[types.HashSize]byte]*types.SigIdent) - return &s, nil -} - -func (s *ActiveSthSource) Run(ctx context.Context) { - schedule.Every(ctx, s.logParameters.Interval, func(ctx context.Context) { - // get the next stable sth - ictx, _ := context.WithTimeout(ctx, s.logParameters.Deadline) - sth, err := s.Latest(ictx) - if err != nil { - glog.Warningf("cannot rotate without new sth: Latest: %v", err) - return - } - // rotate - s.Lock() - defer s.Unlock() - s.rotate(sth) - }) -} - -func (s *ActiveSthSource) Latest(ctx context.Context) (*types.SignedTreeHead, error) { - trsp, err := s.client.GetLatestSignedLogRoot(ctx, &trillian.GetLatestSignedLogRootRequest{ - LogId: s.logParameters.TreeId, - }) - var lr ttypes.LogRootV1 - if errInner := checkGetLatestSignedLogRoot(s.logParameters, trsp, err, &lr); errInner != nil { - return nil, fmt.Errorf("invalid signed log root response: %v", errInner) - } - return s.logParameters.Sign(NewTreeHeadFromLogRoot(&lr)) -} - -func (s *ActiveSthSource) Stable(_ context.Context) (*types.SignedTreeHead, error) { - s.RLock() - defer s.RUnlock() - return &s.tosign, nil -} - -func (s *ActiveSthSource) Cosigned(_ context.Context) (*types.SignedTreeHead, error) { - s.RLock() - defer s.RUnlock() - return &s.cosigned, nil -} - -func (s *ActiveSthSource) AddCosignature(_ context.Context, vk ed25519.PublicKey, sig *[types.SignatureSize]byte) error { - s.Lock() - defer s.Unlock() - - if msg := s.tosign.TreeHead.Marshal(); !ed25519.Verify(vk, msg, sig[:]) { - return fmt.Errorf("Invalid signature for tree head with timestamp: %d", s.tosign.TreeHead.Timestamp) - } - witness := types.Hash(vk[:]) - if _, ok := s.cosignature[*witness]; ok { - glog.V(3).Infof("received cosignature again (duplicate)") - return nil // duplicate - } - s.cosignature[*witness] = &types.SigIdent{ - Signature: sig, - KeyHash: witness, - } - glog.V(3).Infof("accepted new cosignature") - return nil -} - -// rotate rotates the log's cosigned and stable STH. The caller must aquire the -// source's read-write lock if there are concurrent reads and/or writes. -func (s *ActiveSthSource) rotate(next *types.SignedTreeHead) { - if reflect.DeepEqual(s.cosigned.TreeHead, s.tosign.TreeHead) { - for _, sigident := range s.cosigned.SigIdent[1:] { // skip log sigident - if _, ok := s.cosignature[*sigident.KeyHash]; !ok { - s.cosignature[*sigident.KeyHash] = sigident - } - } - } - var cosignatures []*types.SigIdent - for _, sigident := range s.cosignature { - cosignatures = append(cosignatures, sigident) - } // cosignatures contains all cosignatures, even if repeated tree head - - // Update cosigned tree head - s.cosigned.TreeHead = s.tosign.TreeHead - s.cosigned.SigIdent = append(s.tosign.SigIdent, cosignatures...) - - // Update to-sign tree head - s.tosign = *next - s.cosignature = make(map[[types.HashSize]byte]*types.SigIdent) - glog.V(3).Infof("rotated sth") -} diff --git a/sth_test.go b/sth_test.go deleted file mode 100644 index 0942ea1..0000000 --- a/sth_test.go +++ /dev/null @@ -1,466 +0,0 @@ -package stfe - -import ( - "context" - "crypto" - "fmt" - "reflect" - "testing" - - "github.com/golang/mock/gomock" - cttestdata "github.com/google/certificate-transparency-go/trillian/testdata" - "github.com/google/trillian" - "github.com/system-transparency/stfe/testdata" - "github.com/system-transparency/stfe/types" -) - -func TestNewActiveSthSource(t *testing.T) { - for _, table := range []struct { - description string - signer crypto.Signer - trsp *trillian.GetLatestSignedLogRootResponse - terr error - wantErr bool - wantCosi *types.StItem // current cosigned sth - wantStable *types.StItem // next stable sth that signatures are collected for - }{ - { - description: "invalid: no Trillian response", - signer: cttestdata.NewSignerWithFixedSig(nil, testdata.Signature), - terr: fmt.Errorf("internal server error"), - wantErr: true, - }, - { - description: "valid", - signer: cttestdata.NewSignerWithFixedSig(nil, testdata.Signature), - trsp: testdata.DefaultTSlr(t), - wantCosi: testdata.DefaultCosth(t, testdata.Ed25519VkLog, nil), - wantStable: testdata.DefaultCosth(t, testdata.Ed25519VkLog, nil), - }, - } { - func() { // run deferred functions at the end of each iteration - ti := newTestInstance(t, table.signer) - defer ti.ctrl.Finish() - ti.client.EXPECT().GetLatestSignedLogRoot(newDeadlineMatcher(), gomock.Any()).Return(table.trsp, table.terr) - source, err := NewActiveSthSource(ti.client, ti.instance.LogParameters) - if got, want := err != nil, table.wantErr; got != want { - t.Errorf("got error %v but wanted %v in test %q: %v", got, want, table.description, err) - } - if err != nil { - return - } - - if got, want := source.currCosth, table.wantCosi; !reflect.DeepEqual(got, want) { - t.Errorf("got cosigned sth\n%v\n\tbut wanted\n%v\n\tin test %q", got, want, table.description) - } - if got, want := source.nextCosth, table.wantStable; !reflect.DeepEqual(got, want) { - t.Errorf("got stable sth\n%v\n\tbut wanted\n%v\n\tin test %q", got, want, table.description) - } - cosignatureFrom := make(map[[types.NamespaceFingerprintSize]byte]bool) - for _, cosig := range table.wantStable.CosignedTreeHeadV1.Cosignatures { - cosignatureFrom[testdata.Fingerprint(t, &cosig.Namespace)] = true - } - if got, want := source.cosignatureFrom, cosignatureFrom; !reflect.DeepEqual(got, want) { - if got == nil { - t.Errorf("got uninitialized witness map\n%v\n\tbut wanted\n%v\n\tin test %q", nil, want, table.description) - } else { - t.Errorf("got witness map\n%v\n\t but wanted\n%v\n\tin test %q", got, want, table.description) - } - } - }() - } -} - -func TestLatest(t *testing.T) { - for _, table := range []struct { - description string - signer crypto.Signer - trsp *trillian.GetLatestSignedLogRootResponse - terr error - wantErr bool - wantRsp *types.StItem - }{ - { - description: "invalid: no Trillian response", - signer: cttestdata.NewSignerWithFixedSig(nil, testdata.Signature), - terr: fmt.Errorf("internal server error"), - wantErr: true, - }, - { - description: "invalid: no signature", - signer: cttestdata.NewSignerWithErr(nil, fmt.Errorf("signing failed")), - terr: fmt.Errorf("internal server error"), - wantErr: true, - }, - { - description: "valid", - signer: cttestdata.NewSignerWithFixedSig(nil, testdata.Signature), - trsp: testdata.DefaultTSlr(t), - wantRsp: testdata.DefaultSth(t, testdata.Ed25519VkLog), - }, - } { - func() { // run deferred functions at the end of each iteration - ti := newTestInstance(t, table.signer) - defer ti.ctrl.Finish() - ti.client.EXPECT().GetLatestSignedLogRoot(gomock.Any(), gomock.Any()).Return(table.trsp, table.terr) // no deadline matcher because context is set by the caller of Latest(), i.e., this test on the line below - sth, err := ti.instance.SthSource.Latest(context.Background()) - if got, want := err != nil, table.wantErr; got != want { - t.Errorf("got error %v but wanted %v in test %q: %v", got, want, table.description, err) - } - if err != nil { - return - } - if got, want := sth, table.wantRsp; !reflect.DeepEqual(got, want) { - t.Errorf("got\n%v\n\tbut wanted\n%v\n\t in test %q", got, want, table.description) - } - }() - } -} - -func TestStable(t *testing.T) { - for _, table := range []struct { - description string - source SthSource - wantRsp *types.StItem - wantErr bool - }{ - { - description: "invalid: no stable sth", - source: &ActiveSthSource{}, - wantErr: true, - }, - { - description: "valid", - source: &ActiveSthSource{ - nextCosth: testdata.DefaultCosth(t, testdata.Ed25519VkLog, nil), - }, - wantRsp: testdata.DefaultSth(t, testdata.Ed25519VkLog), - }, - } { - sth, err := table.source.Stable(context.Background()) - if got, want := err != nil, table.wantErr; got != want { - t.Errorf("got error %v but wanted %v in test %q: %v", got, want, table.description, err) - } - if err != nil { - continue - } - if got, want := sth, table.wantRsp; !reflect.DeepEqual(got, want) { - t.Errorf("got\n%v\n\t but wanted\n%v\n\t in test %q", got, want, table.description) - } - } -} - -func TestCosigned(t *testing.T) { - for _, table := range []struct { - description string - source SthSource - wantRsp *types.StItem - wantErr bool - }{ - { - description: "invalid: no cosigned sth: nil", - source: &ActiveSthSource{}, - wantErr: true, - }, - { - description: "valid", - source: &ActiveSthSource{ - currCosth: testdata.DefaultCosth(t, testdata.Ed25519VkLog, [][32]byte{testdata.Ed25519VkWitness}), - }, - wantRsp: testdata.DefaultCosth(t, testdata.Ed25519VkLog, [][32]byte{testdata.Ed25519VkWitness}), - }, - } { - cosi, err := table.source.Cosigned(context.Background()) - if got, want := err != nil, table.wantErr; got != want { - t.Errorf("got error %v but wanted %v in test %q: %v", got, want, table.description, err) - } - if err != nil { - continue - } - if got, want := cosi, table.wantRsp; !reflect.DeepEqual(got, want) { - t.Errorf("got\n%v\n\tbut wanted\n%v\n\tin test %q", got, want, table.description) - } - } -} - -func TestAddCosignature(t *testing.T) { - for _, table := range []struct { - description string - source *ActiveSthSource - req *types.StItem - wantWit []*types.Namespace - wantErr bool - }{ - { - description: "invalid: cosignature must target the stable sth", - source: &ActiveSthSource{ - nextCosth: testdata.DefaultCosth(t, testdata.Ed25519VkLog, nil), - cosignatureFrom: make(map[[types.NamespaceFingerprintSize]byte]bool), - }, - req: testdata.DefaultCosth(t, testdata.Ed25519VkLog2, [][32]byte{testdata.Ed25519VkWitness}), - wantErr: true, - }, - { - description: "valid: adding duplicate into a pool of cosignatures", - source: &ActiveSthSource{ - nextCosth: testdata.DefaultCosth(t, testdata.Ed25519VkLog, [][32]byte{testdata.Ed25519VkWitness}), - cosignatureFrom: map[[types.NamespaceFingerprintSize]byte]bool{ - testdata.Fingerprint(t, testdata.NewNamespace(t, testdata.Ed25519VkWitness)): true, - }, - }, - req: testdata.DefaultCosth(t, testdata.Ed25519VkLog, [][32]byte{testdata.Ed25519VkWitness}), - wantWit: []*types.Namespace{testdata.NewNamespace(t, testdata.Ed25519VkWitness)}, - }, - { - description: "valid: adding into an empty pool of cosignatures", - source: &ActiveSthSource{ - nextCosth: testdata.DefaultCosth(t, testdata.Ed25519VkLog, nil), - cosignatureFrom: make(map[[types.NamespaceFingerprintSize]byte]bool), - }, - req: testdata.DefaultCosth(t, testdata.Ed25519VkLog, [][32]byte{testdata.Ed25519VkWitness}), - wantWit: []*types.Namespace{testdata.NewNamespace(t, testdata.Ed25519VkWitness)}, - }, - { - description: "valid: adding into a pool of cosignatures", - source: &ActiveSthSource{ - nextCosth: testdata.DefaultCosth(t, testdata.Ed25519VkLog, [][32]byte{testdata.Ed25519VkWitness}), - cosignatureFrom: map[[types.NamespaceFingerprintSize]byte]bool{ - testdata.Fingerprint(t, testdata.NewNamespace(t, testdata.Ed25519VkWitness)): true, - }, - }, - req: testdata.DefaultCosth(t, testdata.Ed25519VkLog, [][32]byte{testdata.Ed25519VkWitness2}), - wantWit: []*types.Namespace{testdata.NewNamespace(t, testdata.Ed25519VkWitness), testdata.NewNamespace(t, testdata.Ed25519VkWitness2)}, - }, - } { - err := table.source.AddCosignature(context.Background(), table.req) - if got, want := err != nil, table.wantErr; got != want { - t.Errorf("got error %v but wanted %v in test %q: %v", got, want, table.description, err) - } - if err != nil { - continue - } - - // Check that the next cosigned sth is updated - var sigs []types.SignatureV1 - for _, wit := range table.wantWit { - sigs = append(sigs, types.SignatureV1{ - Namespace: *wit, - Signature: testdata.Signature, - }) - } - if got, want := table.source.nextCosth, types.NewCosignedTreeHeadV1(testdata.DefaultSth(t, testdata.Ed25519VkLog).SignedTreeHeadV1, sigs); !reflect.DeepEqual(got, want) { - t.Errorf("got\n%v\n\tbut wanted\n%v\n\tin test %q", got, want, table.description) - } - // Check that the map tracking witness signatures is updated - if got, want := len(table.source.cosignatureFrom), len(table.wantWit); got != want { - t.Errorf("witness map got %d cosignatures but wanted %d in test %q", got, want, table.description) - } else { - for _, wit := range table.wantWit { - if _, ok := table.source.cosignatureFrom[testdata.Fingerprint(t, wit)]; !ok { - t.Errorf("missing signature from witness %X in test %q", testdata.Fingerprint(t, wit), table.description) - } - } - } - } -} - -func TestRotate(t *testing.T) { - // distinct sths - sth1 := testdata.DefaultSth(t, testdata.Ed25519VkLog) - sth2 := testdata.DefaultSth(t, testdata.Ed25519VkLog2) - sth3 := testdata.DefaultSth(t, testdata.Ed25519VkLog3) - // distinct witnesses - wit1 := testdata.NewNamespace(t, testdata.Ed25519VkWitness) - wit2 := testdata.NewNamespace(t, testdata.Ed25519VkWitness2) - wit3 := testdata.NewNamespace(t, testdata.Ed25519VkWitness3) - for _, table := range []struct { - description string - source *ActiveSthSource - fixedSth *types.StItem - wantCurrSth *types.StItem - wantNextSth *types.StItem - wantWit []*types.Namespace - }{ - { - description: "not repeated cosigned and not repeated stable", - source: &ActiveSthSource{ - currCosth: types.NewCosignedTreeHeadV1(sth1.SignedTreeHeadV1, nil), - nextCosth: types.NewCosignedTreeHeadV1(sth2.SignedTreeHeadV1, []types.SignatureV1{ - types.SignatureV1{ - Namespace: *wit1, - Signature: testdata.Signature, - }, - }), - cosignatureFrom: map[[types.NamespaceFingerprintSize]byte]bool{ - testdata.Fingerprint(t, wit1): true, - }, - }, - fixedSth: sth3, - wantCurrSth: types.NewCosignedTreeHeadV1(sth2.SignedTreeHeadV1, []types.SignatureV1{ - types.SignatureV1{ - Namespace: *wit1, - Signature: testdata.Signature, - }, - }), - wantNextSth: types.NewCosignedTreeHeadV1(sth3.SignedTreeHeadV1, nil), - wantWit: nil, // no cosignatures for the next stable sth yet - }, - { - description: "not repeated cosigned and repeated stable", - source: &ActiveSthSource{ - currCosth: types.NewCosignedTreeHeadV1(sth1.SignedTreeHeadV1, nil), - nextCosth: types.NewCosignedTreeHeadV1(sth2.SignedTreeHeadV1, []types.SignatureV1{ - types.SignatureV1{ - Namespace: *wit1, - Signature: testdata.Signature, - }, - }), - cosignatureFrom: map[[types.NamespaceFingerprintSize]byte]bool{ - testdata.Fingerprint(t, wit1): true, - }, - }, - fixedSth: sth2, - wantCurrSth: types.NewCosignedTreeHeadV1(sth2.SignedTreeHeadV1, []types.SignatureV1{ - types.SignatureV1{ - Namespace: *wit1, - Signature: testdata.Signature, - }, - }), - wantNextSth: types.NewCosignedTreeHeadV1(sth2.SignedTreeHeadV1, []types.SignatureV1{ - types.SignatureV1{ - Namespace: *wit1, - Signature: testdata.Signature, - }, - }), - wantWit: []*types.Namespace{wit1}, - }, - { - description: "repeated cosigned and not repeated stable", - source: &ActiveSthSource{ - currCosth: types.NewCosignedTreeHeadV1(sth1.SignedTreeHeadV1, []types.SignatureV1{ - types.SignatureV1{ - Namespace: *wit1, - Signature: testdata.Signature, - }, - types.SignatureV1{ - Namespace: *wit2, - Signature: testdata.Signature, - }, - }), - nextCosth: types.NewCosignedTreeHeadV1(sth1.SignedTreeHeadV1, []types.SignatureV1{ - types.SignatureV1{ - Namespace: *wit2, - Signature: testdata.Signature, - }, - types.SignatureV1{ - Namespace: *wit3, - Signature: testdata.Signature, - }, - }), - cosignatureFrom: map[[types.NamespaceFingerprintSize]byte]bool{ - testdata.Fingerprint(t, wit2): true, - testdata.Fingerprint(t, wit3): true, - }, - }, - fixedSth: sth3, - wantCurrSth: types.NewCosignedTreeHeadV1(sth1.SignedTreeHeadV1, []types.SignatureV1{ - types.SignatureV1{ - Namespace: *wit2, - Signature: testdata.Signature, - }, - types.SignatureV1{ - Namespace: *wit3, - Signature: testdata.Signature, - }, - types.SignatureV1{ - Namespace: *wit1, - Signature: testdata.Signature, - }, - }), - wantNextSth: types.NewCosignedTreeHeadV1(sth3.SignedTreeHeadV1, nil), - wantWit: nil, // no cosignatures for the next stable sth yet - }, - { - description: "repeated cosigned and repeated stable", - source: &ActiveSthSource{ - currCosth: types.NewCosignedTreeHeadV1(sth1.SignedTreeHeadV1, []types.SignatureV1{ - types.SignatureV1{ - Namespace: *wit1, - Signature: testdata.Signature, - }, - types.SignatureV1{ - Namespace: *wit2, - Signature: testdata.Signature, - }, - }), - nextCosth: types.NewCosignedTreeHeadV1(sth1.SignedTreeHeadV1, []types.SignatureV1{ - types.SignatureV1{ - Namespace: *wit2, - Signature: testdata.Signature, - }, - types.SignatureV1{ - Namespace: *wit3, - Signature: testdata.Signature, - }, - }), - cosignatureFrom: map[[types.NamespaceFingerprintSize]byte]bool{ - testdata.Fingerprint(t, wit2): true, - testdata.Fingerprint(t, wit3): true, - }, - }, - fixedSth: sth1, - wantCurrSth: types.NewCosignedTreeHeadV1(sth1.SignedTreeHeadV1, []types.SignatureV1{ - types.SignatureV1{ - Namespace: *wit2, - Signature: testdata.Signature, - }, - types.SignatureV1{ - Namespace: *wit3, - Signature: testdata.Signature, - }, - types.SignatureV1{ - Namespace: *wit1, - Signature: testdata.Signature, - }, - }), - wantNextSth: types.NewCosignedTreeHeadV1(sth1.SignedTreeHeadV1, []types.SignatureV1{ - types.SignatureV1{ - Namespace: *wit2, - Signature: testdata.Signature, - }, - types.SignatureV1{ - Namespace: *wit3, - Signature: testdata.Signature, - }, - types.SignatureV1{ - Namespace: *wit1, - Signature: testdata.Signature, - }, - }), - wantWit: []*types.Namespace{wit1, wit2, wit3}, - }, - } { - table.source.rotate(table.fixedSth) - if got, want := table.source.currCosth, table.wantCurrSth; !reflect.DeepEqual(got, want) { - t.Errorf("got currCosth\n%v\n\tbut wanted \n%v\n\tin test %q", got, want, table.description) - } - if got, want := table.source.nextCosth, table.wantNextSth; !reflect.DeepEqual(got, want) { - t.Errorf("got nextCosth\n%v\n\tbut wanted\n%v\n\tin test %q", got, want, table.description) - } - if got, want := len(table.source.cosignatureFrom), len(table.wantWit); got != want { - t.Errorf("witness map got %d cosignatures but wanted %d in test %q", got, want, table.description) - } else { - for _, wit := range table.wantWit { - if _, ok := table.source.cosignatureFrom[testdata.Fingerprint(t, wit)]; !ok { - t.Errorf("missing signature from witness %X in test %q", testdata.Fingerprint(t, wit), table.description) - } - } - } - // check that adding cosignatures to stable will not effect cosigned sth - wantLen := len(table.source.currCosth.CosignedTreeHeadV1.Cosignatures) - table.source.nextCosth.CosignedTreeHeadV1.Cosignatures = append(table.source.nextCosth.CosignedTreeHeadV1.Cosignatures, types.SignatureV1{Namespace: *wit1, Signature: testdata.Signature}) - if gotLen := len(table.source.currCosth.CosignedTreeHeadV1.Cosignatures); gotLen != wantLen { - t.Errorf("adding cosignatures to the stable sth modifies the fixated cosigned sth in test %q", table.description) - } - } -} diff --git a/testdata/data.go b/testdata/data.go deleted file mode 100644 index ac958e5..0000000 --- a/testdata/data.go +++ /dev/null @@ -1,287 +0,0 @@ -package testdata - -import ( - "bytes" - "testing" - "time" - - "crypto/ed25519" - - "github.com/google/trillian" - ttypes "github.com/google/trillian/types" - "github.com/system-transparency/stfe/types" - "google.golang.org/grpc/codes" - "google.golang.org/grpc/status" -) - -var ( - Ed25519VkLog = [32]byte{} - Ed25519VkLog2 = [32]byte{1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1} - Ed25519VkLog3 = [32]byte{2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2} - //Ed25519VkWitness = [32]byte{3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3} - // Ed25519VkWitness2 = [32]byte{4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4} - Ed25519VkWitness3 = [32]byte{5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5} - //Ed25519VkSubmitter = [32]byte{6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6} - - TreeId = int64(0) - Prefix = "test" - MaxRange = int64(3) - Interval = time.Second * 10 - Deadline = time.Second * 5 - - Timestamp = uint64(0) - TreeSize = uint64(0) - Extension = make([]byte, 0) - NodeHash = make([]byte, 32) - Signature = make([]byte, 64) - Identifier = []byte("foobar-1.2.3") - Checksum = make([]byte, 32) - Index = int64(0) - HashPath = [][]byte{ - NodeHash, - } - NodePath = []types.NodeHash{ - types.NodeHash{NodeHash}, - } - LeafHash = [32]byte{} - - // TODO: make these unique and load more pretty maybe - Ed25519SkWitness = [64]byte{230, 122, 195, 152, 194, 195, 147, 153, 80, 120, 153, 79, 102, 27, 52, 187, 136, 218, 150, 234, 107, 9, 167, 4, 92, 21, 11, 113, 42, 29, 129, 69, 75, 60, 249, 150, 229, 93, 75, 32, 103, 126, 244, 37, 53, 182, 68, 82, 249, 109, 49, 94, 10, 19, 146, 244, 58, 191, 169, 107, 78, 37, 45, 210} - Ed25519VkWitness = [32]byte{75, 60, 249, 150, 229, 93, 75, 32, 103, 126, 244, 37, 53, 182, 68, 82, 249, 109, 49, 94, 10, 19, 146, 244, 58, 191, 169, 107, 78, 37, 45, 210} - - Ed25519SkWitness2 = [64]byte{98, 65, 92, 117, 33, 167, 138, 36, 252, 147, 87, 173, 44, 62, 17, 66, 126, 70, 218, 87, 91, 148, 64, 194, 241, 248, 62, 90, 140, 122, 234, 76, 144, 6, 250, 185, 37, 217, 77, 201, 180, 42, 81, 37, 165, 27, 22, 32, 25, 8, 156, 228, 78, 207, 208, 18, 91, 77, 189, 51, 112, 31, 237, 6} - Ed25519VkWitness2 = [32]byte{144, 6, 250, 185, 37, 217, 77, 201, 180, 42, 81, 37, 165, 27, 22, 32, 25, 8, 156, 228, 78, 207, 208, 18, 91, 77, 189, 51, 112, 31, 237, 6} - - Ed25519SkSubmitter = [64]byte{230, 122, 195, 152, 194, 195, 147, 153, 80, 120, 153, 79, 102, 27, 52, 187, 136, 218, 150, 234, 107, 9, 167, 4, 92, 21, 11, 113, 42, 29, 129, 69, 75, 60, 249, 150, 229, 93, 75, 32, 103, 126, 244, 37, 53, 182, 68, 82, 249, 109, 49, 94, 10, 19, 146, 244, 58, 191, 169, 107, 78, 37, 45, 210} - Ed25519VkSubmitter = [32]byte{75, 60, 249, 150, 229, 93, 75, 32, 103, 126, 244, 37, 53, 182, 68, 82, 249, 109, 49, 94, 10, 19, 146, 244, 58, 191, 169, 107, 78, 37, 45, 210} - Ed25519SkSubmitter2 = [64]byte{98, 65, 92, 117, 33, 167, 138, 36, 252, 147, 87, 173, 44, 62, 17, 66, 126, 70, 218, 87, 91, 148, 64, 194, 241, 248, 62, 90, 140, 122, 234, 76, 144, 6, 250, 185, 37, 217, 77, 201, 180, 42, 81, 37, 165, 27, 22, 32, 25, 8, 156, 228, 78, 207, 208, 18, 91, 77, 189, 51, 112, 31, 237, 6} - Ed25519VkSubmitter2 = [32]byte{144, 6, 250, 185, 37, 217, 77, 201, 180, 42, 81, 37, 165, 27, 22, 32, 25, 8, 156, 228, 78, 207, 208, 18, 91, 77, 189, 51, 112, 31, 237, 6} -) - -// TODO: reorder and docdoc where need be -// -// Helpers that must create default values for different STFE types -// - -func DefaultCosth(t *testing.T, logVk [32]byte, witVk [][32]byte) *types.StItem { - t.Helper() - cosigs := make([]types.SignatureV1, 0) - for _, vk := range witVk { - cosigs = append(cosigs, types.SignatureV1{*NewNamespace(t, vk), Signature}) - } - return types.NewCosignedTreeHeadV1(DefaultSth(t, logVk).SignedTreeHeadV1, cosigs) -} - -func DefaultSth(t *testing.T, vk [32]byte) *types.StItem { - t.Helper() - return types.NewSignedTreeHeadV1(DefaultTh(t), DefaultSig(t, vk)) -} - -func DefaultSignedChecksum(t *testing.T, vk [32]byte) *types.StItem { - t.Helper() - return types.NewSignedChecksumV1(DefaultChecksum(t), DefaultSig(t, vk)) -} - -func DefaultTh(t *testing.T) *types.TreeHeadV1 { - t.Helper() - return types.NewTreeHeadV1(Timestamp, TreeSize, NodeHash, Extension) -} - -func DefaultSig(t *testing.T, vk [32]byte) *types.SignatureV1 { - t.Helper() - return &types.SignatureV1{*NewNamespace(t, vk), Signature} -} - -func DefaultChecksum(t *testing.T) *types.ChecksumV1 { - t.Helper() - return &types.ChecksumV1{Identifier, Checksum} -} - -func AddCosignatureBuffer(t *testing.T, sth *types.StItem, sk *[64]byte, vk *[32]byte) *bytes.Buffer { - t.Helper() - var cosigs []types.SignatureV1 - if vk != nil { - cosigs = []types.SignatureV1{ - types.SignatureV1{ - Namespace: *NewNamespace(t, *vk), - Signature: ed25519.Sign(ed25519.PrivateKey((*sk)[:]), marshal(t, *sth.SignedTreeHeadV1)), - }, - } - } - return bytes.NewBuffer(marshal(t, *types.NewCosignedTreeHeadV1(sth.SignedTreeHeadV1, cosigs))) -} - -func AddSignedChecksumBuffer(t *testing.T, sk [64]byte, vk [32]byte) *bytes.Buffer { - t.Helper() - data := DefaultChecksum(t) - return bytes.NewBuffer(marshal(t, *types.NewSignedChecksumV1( - data, - &types.SignatureV1{ - Namespace: *NewNamespace(t, vk), - Signature: ed25519.Sign(ed25519.PrivateKey(sk[:]), marshal(t, *data)), - }, - ))) -} - -func NewNamespacePool(t *testing.T, namespaces []*types.Namespace) *types.NamespacePool { - pool, err := types.NewNamespacePool(namespaces) - if err != nil { - t.Fatalf("must make namespace pool: %v", err) - } - return pool -} - -func NewNamespace(t *testing.T, vk [32]byte) *types.Namespace { - namespace, err := types.NewNamespaceEd25519V1(vk[:]) - if err != nil { - t.Fatalf("must make Ed25519V1 namespace: %v", err) - } - return namespace -} - -// -// Helpers that must create default values for different Trillian types -// - -// DefaultTLr creates a default Trillian log root -func DefaultTLr(t *testing.T) *ttypes.LogRootV1 { - t.Helper() - return Tlr(t, TreeSize, Timestamp, NodeHash) -} - -// Tlr creates a Trillian log root -func Tlr(t *testing.T, size, timestamp uint64, hash []byte) *ttypes.LogRootV1 { - t.Helper() - return &ttypes.LogRootV1{ - TreeSize: size, - RootHash: hash, - TimestampNanos: timestamp, - Revision: 0, // not used by stfe - Metadata: nil, // not used by stfe - } -} - -// DefaultTSlr creates a default Trillian signed log root -func DefaultTSlr(t *testing.T) *trillian.GetLatestSignedLogRootResponse { - t.Helper() - return Tslr(t, DefaultTLr(t)) -} - -// Tslr creates a Trillian signed log root -func Tslr(t *testing.T, lr *ttypes.LogRootV1) *trillian.GetLatestSignedLogRootResponse { - t.Helper() - b, err := lr.MarshalBinary() - if err != nil { - t.Fatalf("must marshal Trillian log root: %v", err) - } - return &trillian.GetLatestSignedLogRootResponse{ - SignedLogRoot: &trillian.SignedLogRoot{ - KeyHint: nil, // not used by stfe - LogRoot: b, - LogRootSignature: nil, // not used by stfe - }, - Proof: nil, // not used by stfe - } -} - -// DefaultTQlr creates a default Trillian queue leaf response -func DefaultTQlr(t *testing.T, withDupCode bool) *trillian.QueueLeafResponse { - t.Helper() - s := status.New(codes.OK, "ok").Proto() - if withDupCode { - s = status.New(codes.AlreadyExists, "duplicate").Proto() - } - return &trillian.QueueLeafResponse{ - QueuedLeaf: &trillian.QueuedLogLeaf{ - Leaf: &trillian.LogLeaf{ - MerkleLeafHash: nil, // not used by stfe - LeafValue: marshal(t, *DefaultSignedChecksum(t, Ed25519VkSubmitter)), - ExtraData: nil, // not used by stfe - LeafIndex: 0, // not applicable (log is not pre-ordered) - LeafIdentityHash: nil, // not used by stfe - }, - Status: s, - }, - } -} - -// DefaultTglbrr creates a default Trillian get leaves by range response -func DefaultTGlbrr(t *testing.T, start, end int64) *trillian.GetLeavesByRangeResponse { - t.Helper() - leaves := make([]*trillian.LogLeaf, 0, end-start+1) - for i, n := start, end+1; i < n; i++ { - leaves = append(leaves, &trillian.LogLeaf{ - MerkleLeafHash: nil, // not usedb y stfe - LeafValue: marshal(t, *DefaultSignedChecksum(t, Ed25519VkSubmitter)), - ExtraData: nil, // not used by stfe - LeafIndex: i, - LeafIdentityHash: nil, // not used by stfe - }) - } - return &trillian.GetLeavesByRangeResponse{ - Leaves: leaves, - SignedLogRoot: Tslr(t, Tlr(t, uint64(end)+1, Timestamp, NodeHash)).SignedLogRoot, - } -} - -func DefaultStItemList(t *testing.T, start, end uint64) *types.StItemList { - items := make([]types.StItem, 0, end-start+1) - for i, n := start, end+1; i < n; i++ { - items = append(items, *DefaultSignedChecksum(t, Ed25519VkSubmitter)) - } - return &types.StItemList{items} -} - -// DefaultTGipbhr creates a default Trillian get inclusion proof by hash response -func DefaultTGipbhr(t *testing.T) *trillian.GetInclusionProofByHashResponse { - t.Helper() - return &trillian.GetInclusionProofByHashResponse{ - Proof: []*trillian.Proof{ - &trillian.Proof{ - LeafIndex: Index, - Hashes: HashPath, - }, - }, - SignedLogRoot: nil, // not used by stfe - } -} - -func DefaultInclusionProof(t *testing.T, size uint64) *types.StItem { - return types.NewInclusionProofV1(NewNamespace(t, Ed25519VkLog), size, uint64(Index), NodePath) -} - -// DefaultTGcpr creates a default Trillian get consistency proof response -func DefaultTGcpr(t *testing.T) *trillian.GetConsistencyProofResponse { - t.Helper() - return &trillian.GetConsistencyProofResponse{ - Proof: &trillian.Proof{ - LeafIndex: 0, // not applicable for consistency proofs - Hashes: HashPath, - }, - SignedLogRoot: nil, // not used by stfe - } -} - -func DefaultConsistencyProof(t *testing.T, first, second uint64) *types.StItem { - return types.NewConsistencyProofV1(NewNamespace(t, Ed25519VkLog), first, second, NodePath) -} - -// -// Other helpers -// - -func Fingerprint(t *testing.T, namespace *types.Namespace) [types.NamespaceFingerprintSize]byte { - fpr, err := namespace.Fingerprint() - if err != nil { - t.Fatalf("must have namespace fingerprint: %v", err) - } - return *fpr -} - -func marshal(t *testing.T, i interface{}) []byte { - b, err := types.Marshal(i) - if err != nil { - t.Fatalf("must marshal interface: %v", err) - } - return b -} diff --git a/trillian.go b/trillian.go deleted file mode 100644 index f358d4d..0000000 --- a/trillian.go +++ /dev/null @@ -1,125 +0,0 @@ -package stfe - -import ( - "fmt" - - "github.com/golang/glog" - "github.com/google/trillian" - "github.com/google/trillian/types" - stfetypes "github.com/system-transparency/stfe/types" - "google.golang.org/grpc/codes" -) - -func checkQueueLeaf(rsp *trillian.QueueLeafResponse, err error) error { - if err != nil { - return fmt.Errorf("Trillian error: %v", err) - } - if rsp == nil { - return fmt.Errorf("Trillian error: empty response") - } - if rsp.QueuedLeaf == nil { - return fmt.Errorf("Trillian error: empty QueuedLeaf") - } - if codes.Code(rsp.QueuedLeaf.GetStatus().GetCode()) == codes.AlreadyExists { - glog.V(3).Infof("queued leaf is a duplicate => %X", rsp.QueuedLeaf.Leaf.LeafValue) - } - return nil -} - -func checkGetLeavesByRange(req *stfetypes.LeavesRequest, rsp *trillian.GetLeavesByRangeResponse, err error) error { - if err != nil { - return fmt.Errorf("Trillian Error: %v", err) - } - if rsp == nil { - return fmt.Errorf("Trillian error: empty response") - } - if rsp.SignedLogRoot == nil { - return fmt.Errorf("Trillian error: no signed log root") - } - if rsp.SignedLogRoot.LogRoot == nil { - return fmt.Errorf("Trillian error: no log root") - } - if len(rsp.Leaves) == 0 { - return fmt.Errorf("Trillian error: no leaves") - } - if len(rsp.Leaves) > int(req.EndSize-req.StartSize+1) { - return fmt.Errorf("too many leaves: %d for [%d,%d]", len(rsp.Leaves), req.StartSize, req.EndSize) - } - - // Ensure that a bad start parameter results in an error - var lr types.LogRootV1 - if err := lr.UnmarshalBinary(rsp.SignedLogRoot.LogRoot); err != nil { - return fmt.Errorf("cannot unmarshal log root: %v", err) - } - if uint64(req.StartSize) >= lr.TreeSize { - return fmt.Errorf("invalid start(%d): tree size is %d", req.StartSize, lr.TreeSize) - } - - // Ensure that we got and return expected leaf indices - for i, leaf := range rsp.Leaves { - if got, want := leaf.LeafIndex, int64(req.StartSize+uint64(i)); got != want { - return fmt.Errorf("invalid leaf index(%d): wanted %d", got, want) - } - } - return nil -} - -func checkGetInclusionProofByHash(lp *LogParameters, rsp *trillian.GetInclusionProofByHashResponse, err error) error { - if err != nil { - return fmt.Errorf("Trillian Error: %v", err) - } - if rsp == nil { - return fmt.Errorf("Trillian error: empty response") - } - if len(rsp.Proof) == 0 { - return fmt.Errorf("Trillian error: no proofs") - } - if rsp.Proof[0] == nil { - return fmt.Errorf("Trillian error: no proof") - } - return checkHashPath(lp.HashType.Size(), rsp.Proof[0].Hashes) -} - -func checkGetConsistencyProof(lp *LogParameters, rsp *trillian.GetConsistencyProofResponse, err error) error { - if err != nil { - return fmt.Errorf("Trillian Error: %v", err) - } - if rsp == nil { - return fmt.Errorf("Trillian error: empty response") - } - if rsp.Proof == nil { - return fmt.Errorf("Trillian error: no proof") - } - return checkHashPath(lp.HashType.Size(), rsp.Proof.Hashes) -} - -func checkGetLatestSignedLogRoot(lp *LogParameters, rsp *trillian.GetLatestSignedLogRootResponse, err error, out *types.LogRootV1) error { - if err != nil { - return fmt.Errorf("Trillian Error: %v", err) - } - if rsp == nil { - return fmt.Errorf("Trillian error: empty response") - } - if rsp.SignedLogRoot == nil { - return fmt.Errorf("Trillian error: no signed log root") - } - if rsp.SignedLogRoot.LogRoot == nil { - return fmt.Errorf("Trillian error: no log root") - } - if err := out.UnmarshalBinary(rsp.SignedLogRoot.LogRoot); err != nil { - return fmt.Errorf("cannot unmarshal log root: %v", err) - } - if len(out.RootHash) != lp.HashType.Size() { - return fmt.Errorf("invalid root hash: %v", out.RootHash) - } - return nil -} - -func checkHashPath(hashSize int, path [][]byte) error { - for _, hash := range path { - if len(hash) != hashSize { - return fmt.Errorf("invalid proof: %v", path) - } - } - return nil -} diff --git a/trillian/client.go b/trillian/client.go deleted file mode 100644 index 9ea6a4a..0000000 --- a/trillian/client.go +++ /dev/null @@ -1,178 +0,0 @@ -package trillian - -import ( - "context" - "fmt" - - "github.com/golang/glog" - "github.com/google/trillian" - ttypes "github.com/google/trillian/types" - "github.com/system-transparency/stfe/types" - "google.golang.org/grpc/codes" -) - -type Client interface { - AddLeaf(context.Context, *types.LeafRequest) error - GetConsistencyProof(context.Context, *types.ConsistencyProofRequest) (*types.ConsistencyProof, error) - GetTreeHead(context.Context) (*types.TreeHead, error) - GetInclusionProof(context.Context, *types.InclusionProofRequest) (*types.InclusionProof, error) - GetLeaves(context.Context, *types.LeavesRequest) (*types.LeafList, error) -} - -// TrillianClient is a wrapper around the Trillian gRPC client. -type TrillianClient struct { - // TreeID is a Merkle tree identifier that Trillian uses - TreeID int64 - - // GRPC is a Trillian gRPC client - GRPC trillian.TrillianLogClient -} - -func (c *TrillianClient) AddLeaf(ctx context.Context, req *types.LeafRequest) error { - leaf := types.Leaf{ - Message: req.Message, - SigIdent: types.SigIdent{ - Signature: req.Signature, - KeyHash: types.Hash(req.VerificationKey[:]), - }, - } - serialized := leaf.Marshal() - - glog.V(3).Infof("queueing leaf request: %x", types.HashLeaf(serialized)) - rsp, err := c.GRPC.QueueLeaf(ctx, &trillian.QueueLeafRequest{ - LogId: c.TreeID, - Leaf: &trillian.LogLeaf{ - LeafValue: serialized, - }, - }) - if err != nil { - return fmt.Errorf("backend failure: %v", err) - } - if rsp == nil { - return fmt.Errorf("no response") - } - if rsp.QueuedLeaf == nil { - return fmt.Errorf("no queued leaf") - } - if codes.Code(rsp.QueuedLeaf.GetStatus().GetCode()) == codes.AlreadyExists { - return fmt.Errorf("leaf is already queued or included") - } - return nil -} - -func (c *TrillianClient) GetTreeHead(ctx context.Context) (*types.TreeHead, error) { - rsp, err := c.GRPC.GetLatestSignedLogRoot(ctx, &trillian.GetLatestSignedLogRootRequest{ - LogId: c.TreeID, - }) - if err != nil { - return nil, fmt.Errorf("backend failure: %v", err) - } - if rsp == nil { - return nil, fmt.Errorf("no response") - } - if rsp.SignedLogRoot == nil { - return nil, fmt.Errorf("no signed log root") - } - if rsp.SignedLogRoot.LogRoot == nil { - return nil, fmt.Errorf("no log root") - } - var r ttypes.LogRootV1 - if err := r.UnmarshalBinary(rsp.SignedLogRoot.LogRoot); err != nil { - return nil, fmt.Errorf("no log root: unmarshal failed: %v", err) - } - if len(r.RootHash) != types.HashSize { - return nil, fmt.Errorf("unexpected hash length: %d", len(r.RootHash)) - } - return treeHeadFromLogRoot(&r), nil -} - -func (c *TrillianClient) GetConsistencyProof(ctx context.Context, req *types.ConsistencyProofRequest) (*types.ConsistencyProof, error) { - rsp, err := c.GRPC.GetConsistencyProof(ctx, &trillian.GetConsistencyProofRequest{ - LogId: c.TreeID, - FirstTreeSize: int64(req.OldSize), - SecondTreeSize: int64(req.NewSize), - }) - if err != nil { - return nil, fmt.Errorf("backend failure: %v", err) - } - if rsp == nil { - return nil, fmt.Errorf("no response") - } - if rsp.Proof == nil { - return nil, fmt.Errorf("no consistency proof") - } - if len(rsp.Proof.Hashes) == 0 { - return nil, fmt.Errorf("not a consistency proof: empty") - } - path, err := nodePathFromHashes(rsp.Proof.Hashes) - if err != nil { - return nil, fmt.Errorf("not a consistency proof: %v", err) - } - return &types.ConsistencyProof{ - OldSize: req.OldSize, - NewSize: req.NewSize, - Path: path, - }, nil -} - -func (c *TrillianClient) GetInclusionProof(ctx context.Context, req *types.InclusionProofRequest) (*types.InclusionProof, error) { - rsp, err := c.GRPC.GetInclusionProofByHash(ctx, &trillian.GetInclusionProofByHashRequest{ - LogId: c.TreeID, - LeafHash: req.LeafHash[:], - TreeSize: int64(req.TreeSize), - OrderBySequence: true, - }) - if err != nil { - return nil, fmt.Errorf("backend failure: %v", err) - } - if rsp == nil { - return nil, fmt.Errorf("no response") - } - if len(rsp.Proof) != 1 { - return nil, fmt.Errorf("bad proof count: %d", len(rsp.Proof)) - } - proof := rsp.Proof[0] - if len(proof.Hashes) == 0 { - return nil, fmt.Errorf("not an inclusion proof: empty") - } - path, err := nodePathFromHashes(proof.Hashes) - if err != nil { - return nil, fmt.Errorf("not an inclusion proof: %v", err) - } - return &types.InclusionProof{ - TreeSize: req.TreeSize, - LeafIndex: uint64(proof.LeafIndex), - Path: path, - }, nil -} - -func (c *TrillianClient) GetLeaves(ctx context.Context, req *types.LeavesRequest) (*types.LeafList, error) { - rsp, err := c.GRPC.GetLeavesByRange(ctx, &trillian.GetLeavesByRangeRequest{ - LogId: c.TreeID, - StartIndex: int64(req.StartSize), - Count: int64(req.EndSize-req.StartSize) + 1, - }) - if err != nil { - return nil, fmt.Errorf("backend failure: %v", err) - } - if rsp == nil { - return nil, fmt.Errorf("no response") - } - if got, want := len(rsp.Leaves), int(req.EndSize-req.StartSize+1); got != want { - return nil, fmt.Errorf("unexpected number of leaves: %d", got) - } - var list types.LeafList - for i, leaf := range rsp.Leaves { - leafIndex := int64(req.StartSize + uint64(i)) - if leafIndex != leaf.LeafIndex { - return nil, fmt.Errorf("unexpected leaf(%d): got index %d", leafIndex, leaf.LeafIndex) - } - - var l types.Leaf - if err := l.Unmarshal(leaf.LeafValue); err != nil { - return nil, fmt.Errorf("unexpected leaf(%d): %v", leafIndex, err) - } - list = append(list[:], &l) - } - return &list, nil -} diff --git a/trillian/client_test.go b/trillian/client_test.go deleted file mode 100644 index e9f1ff5..0000000 --- a/trillian/client_test.go +++ /dev/null @@ -1,533 +0,0 @@ -package trillian - -import ( - "context" - "fmt" - "reflect" - "testing" - - "github.com/golang/mock/gomock" - "github.com/google/trillian" - ttypes "github.com/google/trillian/types" - "github.com/system-transparency/stfe/mocks" - "github.com/system-transparency/stfe/types" - "google.golang.org/grpc/codes" - "google.golang.org/grpc/status" -) - -func TestAddLeaf(t *testing.T) { - req := &types.LeafRequest{ - Message: types.Message{ - ShardHint: 0, - Checksum: &[types.HashSize]byte{}, - }, - Signature: &[types.SignatureSize]byte{}, - VerificationKey: &[types.VerificationKeySize]byte{}, - DomainHint: "example.com", - } - for _, table := range []struct { - description string - req *types.LeafRequest - rsp *trillian.QueueLeafResponse - err error - wantErr bool - }{ - { - description: "invalid: backend failure", - req: req, - err: fmt.Errorf("something went wrong"), - wantErr: true, - }, - { - description: "invalid: no response", - req: req, - wantErr: true, - }, - { - description: "invalid: no queued leaf", - req: req, - rsp: &trillian.QueueLeafResponse{}, - wantErr: true, - }, - { - description: "invalid: leaf is already queued or included", - req: req, - rsp: &trillian.QueueLeafResponse{ - QueuedLeaf: &trillian.QueuedLogLeaf{ - Leaf: &trillian.LogLeaf{ - LeafValue: req.Message.Marshal(), - }, - Status: status.New(codes.AlreadyExists, "duplicate").Proto(), - }, - }, - wantErr: true, - }, - { - description: "valid", - req: req, - rsp: &trillian.QueueLeafResponse{ - QueuedLeaf: &trillian.QueuedLogLeaf{ - Leaf: &trillian.LogLeaf{ - LeafValue: req.Message.Marshal(), - }, - Status: status.New(codes.OK, "ok").Proto(), - }, - }, - }, - } { - // Run deferred functions at the end of each iteration - func() { - ctrl := gomock.NewController(t) - defer ctrl.Finish() - grpc := mocks.NewMockTrillianLogClient(ctrl) - grpc.EXPECT().QueueLeaf(gomock.Any(), gomock.Any()).Return(table.rsp, table.err) - client := TrillianClient{GRPC: grpc} - - err := client.AddLeaf(context.Background(), table.req) - if got, want := err != nil, table.wantErr; got != want { - t.Errorf("got error %v but wanted %v in test %q: %v", got, want, table.description, err) - } - }() - } -} - -func TestGetTreeHead(t *testing.T) { - // valid root - root := &ttypes.LogRootV1{ - TreeSize: 0, - RootHash: make([]byte, types.HashSize), - TimestampNanos: 1622585623133599429, - } - buf, err := root.MarshalBinary() - if err != nil { - t.Fatalf("must marshal log root: %v", err) - } - // invalid root - root.RootHash = make([]byte, types.HashSize+1) - bufBadHash, err := root.MarshalBinary() - if err != nil { - t.Fatalf("must marshal log root: %v", err) - } - - for _, table := range []struct { - description string - rsp *trillian.GetLatestSignedLogRootResponse - err error - wantErr bool - wantTh *types.TreeHead - }{ - { - description: "invalid: backend failure", - err: fmt.Errorf("something went wrong"), - wantErr: true, - }, - { - description: "invalid: no response", - wantErr: true, - }, - { - description: "invalid: no signed log root", - rsp: &trillian.GetLatestSignedLogRootResponse{}, - wantErr: true, - }, - { - description: "invalid: no log root", - rsp: &trillian.GetLatestSignedLogRootResponse{ - SignedLogRoot: &trillian.SignedLogRoot{}, - }, - wantErr: true, - }, - { - description: "invalid: no log root: unmarshal failed", - rsp: &trillian.GetLatestSignedLogRootResponse{ - SignedLogRoot: &trillian.SignedLogRoot{ - LogRoot: buf[1:], - }, - }, - wantErr: true, - }, - { - description: "invalid: unexpected hash length", - rsp: &trillian.GetLatestSignedLogRootResponse{ - SignedLogRoot: &trillian.SignedLogRoot{ - LogRoot: bufBadHash, - }, - }, - wantErr: true, - }, - { - description: "valid", - rsp: &trillian.GetLatestSignedLogRootResponse{ - SignedLogRoot: &trillian.SignedLogRoot{ - LogRoot: buf, - }, - }, - wantTh: &types.TreeHead{ - Timestamp: 1622585623, - TreeSize: 0, - RootHash: &[types.HashSize]byte{}, - }, - }, - } { - // Run deferred functions at the end of each iteration - func() { - ctrl := gomock.NewController(t) - defer ctrl.Finish() - grpc := mocks.NewMockTrillianLogClient(ctrl) - grpc.EXPECT().GetLatestSignedLogRoot(gomock.Any(), gomock.Any()).Return(table.rsp, table.err) - client := TrillianClient{GRPC: grpc} - - th, err := client.GetTreeHead(context.Background()) - if got, want := err != nil, table.wantErr; got != want { - t.Errorf("got error %v but wanted %v in test %q: %v", got, want, table.description, err) - } - if err != nil { - return - } - if got, want := th, table.wantTh; !reflect.DeepEqual(got, want) { - t.Errorf("got tree head\n\t%v\nbut wanted\n\t%v\nin test %q", got, want, table.description) - } - }() - } -} - -func TestGetConsistencyProof(t *testing.T) { - req := &types.ConsistencyProofRequest{ - OldSize: 1, - NewSize: 3, - } - for _, table := range []struct { - description string - req *types.ConsistencyProofRequest - rsp *trillian.GetConsistencyProofResponse - err error - wantErr bool - wantProof *types.ConsistencyProof - }{ - { - description: "invalid: backend failure", - req: req, - err: fmt.Errorf("something went wrong"), - wantErr: true, - }, - { - description: "invalid: no response", - req: req, - wantErr: true, - }, - { - description: "invalid: no consistency proof", - req: req, - rsp: &trillian.GetConsistencyProofResponse{}, - wantErr: true, - }, - { - description: "invalid: not a consistency proof (1/2)", - req: req, - rsp: &trillian.GetConsistencyProofResponse{ - Proof: &trillian.Proof{ - Hashes: [][]byte{}, - }, - }, - wantErr: true, - }, - { - description: "invalid: not a consistency proof (2/2)", - req: req, - rsp: &trillian.GetConsistencyProofResponse{ - Proof: &trillian.Proof{ - Hashes: [][]byte{ - make([]byte, types.HashSize), - make([]byte, types.HashSize+1), - }, - }, - }, - wantErr: true, - }, - { - description: "valid", - req: req, - rsp: &trillian.GetConsistencyProofResponse{ - Proof: &trillian.Proof{ - Hashes: [][]byte{ - make([]byte, types.HashSize), - make([]byte, types.HashSize), - }, - }, - }, - wantProof: &types.ConsistencyProof{ - OldSize: 1, - NewSize: 3, - Path: []*[types.HashSize]byte{ - &[types.HashSize]byte{}, - &[types.HashSize]byte{}, - }, - }, - }, - } { - // Run deferred functions at the end of each iteration - func() { - ctrl := gomock.NewController(t) - defer ctrl.Finish() - grpc := mocks.NewMockTrillianLogClient(ctrl) - grpc.EXPECT().GetConsistencyProof(gomock.Any(), gomock.Any()).Return(table.rsp, table.err) - client := TrillianClient{GRPC: grpc} - - proof, err := client.GetConsistencyProof(context.Background(), table.req) - if got, want := err != nil, table.wantErr; got != want { - t.Errorf("got error %v but wanted %v in test %q: %v", got, want, table.description, err) - } - if err != nil { - return - } - if got, want := proof, table.wantProof; !reflect.DeepEqual(got, want) { - t.Errorf("got proof\n\t%v\nbut wanted\n\t%v\nin test %q", got, want, table.description) - } - }() - } -} - -func TestGetInclusionProof(t *testing.T) { - req := &types.InclusionProofRequest{ - TreeSize: 4, - LeafHash: &[types.HashSize]byte{}, - } - for _, table := range []struct { - description string - req *types.InclusionProofRequest - rsp *trillian.GetInclusionProofByHashResponse - err error - wantErr bool - wantProof *types.InclusionProof - }{ - { - description: "invalid: backend failure", - req: req, - err: fmt.Errorf("something went wrong"), - wantErr: true, - }, - { - description: "invalid: no response", - req: req, - wantErr: true, - }, - { - description: "invalid: bad proof count", - req: req, - rsp: &trillian.GetInclusionProofByHashResponse{ - Proof: []*trillian.Proof{ - &trillian.Proof{}, - &trillian.Proof{}, - }, - }, - wantErr: true, - }, - { - description: "invalid: not an inclusion proof (1/2)", - req: req, - rsp: &trillian.GetInclusionProofByHashResponse{ - Proof: []*trillian.Proof{ - &trillian.Proof{ - LeafIndex: 1, - Hashes: [][]byte{}, - }, - }, - }, - wantErr: true, - }, - { - description: "invalid: not an inclusion proof (2/2)", - req: req, - rsp: &trillian.GetInclusionProofByHashResponse{ - Proof: []*trillian.Proof{ - &trillian.Proof{ - LeafIndex: 1, - Hashes: [][]byte{ - make([]byte, types.HashSize), - make([]byte, types.HashSize+1), - }, - }, - }, - }, - wantErr: true, - }, - { - description: "valid", - req: req, - rsp: &trillian.GetInclusionProofByHashResponse{ - Proof: []*trillian.Proof{ - &trillian.Proof{ - LeafIndex: 1, - Hashes: [][]byte{ - make([]byte, types.HashSize), - make([]byte, types.HashSize), - }, - }, - }, - }, - wantProof: &types.InclusionProof{ - TreeSize: 4, - LeafIndex: 1, - Path: []*[types.HashSize]byte{ - &[types.HashSize]byte{}, - &[types.HashSize]byte{}, - }, - }, - }, - } { - // Run deferred functions at the end of each iteration - func() { - ctrl := gomock.NewController(t) - defer ctrl.Finish() - grpc := mocks.NewMockTrillianLogClient(ctrl) - grpc.EXPECT().GetInclusionProofByHash(gomock.Any(), gomock.Any()).Return(table.rsp, table.err) - client := TrillianClient{GRPC: grpc} - - proof, err := client.GetInclusionProof(context.Background(), table.req) - if got, want := err != nil, table.wantErr; got != want { - t.Errorf("got error %v but wanted %v in test %q: %v", got, want, table.description, err) - } - if err != nil { - return - } - if got, want := proof, table.wantProof; !reflect.DeepEqual(got, want) { - t.Errorf("got proof\n\t%v\nbut wanted\n\t%v\nin test %q", got, want, table.description) - } - }() - } -} - -func TestGetLeaves(t *testing.T) { - req := &types.LeavesRequest{ - StartSize: 1, - EndSize: 2, - } - firstLeaf := &types.Leaf{ - Message: types.Message{ - ShardHint: 0, - Checksum: &[types.HashSize]byte{}, - }, - SigIdent: types.SigIdent{ - Signature: &[types.SignatureSize]byte{}, - KeyHash: &[types.HashSize]byte{}, - }, - } - secondLeaf := &types.Leaf{ - Message: types.Message{ - ShardHint: 0, - Checksum: &[types.HashSize]byte{}, - }, - SigIdent: types.SigIdent{ - Signature: &[types.SignatureSize]byte{}, - KeyHash: &[types.HashSize]byte{}, - }, - } - - for _, table := range []struct { - description string - req *types.LeavesRequest - rsp *trillian.GetLeavesByRangeResponse - err error - wantErr bool - wantLeaves *types.LeafList - }{ - { - description: "invalid: backend failure", - req: req, - err: fmt.Errorf("something went wrong"), - wantErr: true, - }, - { - description: "invalid: no response", - req: req, - wantErr: true, - }, - { - description: "invalid: unexpected number of leaves", - req: req, - rsp: &trillian.GetLeavesByRangeResponse{ - Leaves: []*trillian.LogLeaf{ - &trillian.LogLeaf{ - LeafValue: firstLeaf.Marshal(), - LeafIndex: 1, - }, - }, - }, - wantErr: true, - }, - { - description: "invalid: unexpected leaf (1/2)", - req: req, - rsp: &trillian.GetLeavesByRangeResponse{ - Leaves: []*trillian.LogLeaf{ - &trillian.LogLeaf{ - LeafValue: firstLeaf.Marshal(), - LeafIndex: 1, - }, - &trillian.LogLeaf{ - LeafValue: secondLeaf.Marshal(), - LeafIndex: 3, - }, - }, - }, - wantErr: true, - }, - { - description: "invalid: unexpected leaf (2/2)", - req: req, - rsp: &trillian.GetLeavesByRangeResponse{ - Leaves: []*trillian.LogLeaf{ - &trillian.LogLeaf{ - LeafValue: firstLeaf.Marshal(), - LeafIndex: 1, - }, - &trillian.LogLeaf{ - LeafValue: secondLeaf.Marshal()[1:], - LeafIndex: 2, - }, - }, - }, - wantErr: true, - }, - { - description: "valid", - req: req, - rsp: &trillian.GetLeavesByRangeResponse{ - Leaves: []*trillian.LogLeaf{ - &trillian.LogLeaf{ - LeafValue: firstLeaf.Marshal(), - LeafIndex: 1, - }, - &trillian.LogLeaf{ - LeafValue: secondLeaf.Marshal(), - LeafIndex: 2, - }, - }, - }, - wantLeaves: &types.LeafList{ - firstLeaf, - secondLeaf, - }, - }, - } { - // Run deferred functions at the end of each iteration - func() { - ctrl := gomock.NewController(t) - defer ctrl.Finish() - grpc := mocks.NewMockTrillianLogClient(ctrl) - grpc.EXPECT().GetLeavesByRange(gomock.Any(), gomock.Any()).Return(table.rsp, table.err) - client := TrillianClient{GRPC: grpc} - - leaves, err := client.GetLeaves(context.Background(), table.req) - if got, want := err != nil, table.wantErr; got != want { - t.Errorf("got error %v but wanted %v in test %q: %v", got, want, table.description, err) - } - if err != nil { - return - } - if got, want := leaves, table.wantLeaves; !reflect.DeepEqual(got, want) { - t.Errorf("got leaves\n\t%v\nbut wanted\n\t%v\nin test %q", got, want, table.description) - } - }() - } -} diff --git a/trillian/util.go b/trillian/util.go deleted file mode 100644 index 87e64b6..0000000 --- a/trillian/util.go +++ /dev/null @@ -1,33 +0,0 @@ -package trillian - -import ( - "fmt" - - trillian "github.com/google/trillian/types" - siglog "github.com/system-transparency/stfe/types" -) - -func treeHeadFromLogRoot(lr *trillian.LogRootV1) *siglog.TreeHead { - var hash [siglog.HashSize]byte - th := siglog.TreeHead{ - Timestamp: uint64(lr.TimestampNanos / 1000 / 1000 / 1000), - TreeSize: uint64(lr.TreeSize), - RootHash: &hash, - } - copy(th.RootHash[:], lr.RootHash) - return &th -} - -func nodePathFromHashes(hashes [][]byte) ([]*[siglog.HashSize]byte, error) { - var path []*[siglog.HashSize]byte - for _, hash := range hashes { - if len(hash) != siglog.HashSize { - return nil, fmt.Errorf("unexpected hash length: %v", len(hash)) - } - - var h [siglog.HashSize]byte - copy(h[:], hash) - path = append(path, &h) - } - return path, nil -} diff --git a/trillian_test.go b/trillian_test.go deleted file mode 100644 index 1b0c923..0000000 --- a/trillian_test.go +++ /dev/null @@ -1,282 +0,0 @@ -package stfe - -import ( - "fmt" - "testing" - - "github.com/google/trillian" - ttypes "github.com/google/trillian/types" - "github.com/system-transparency/stfe/testdata" - "github.com/system-transparency/stfe/types" -) - -func TestCheckQueueLeaf(t *testing.T) { - for _, table := range []struct { - description string - rsp *trillian.QueueLeafResponse - err error - wantErr bool - }{ - { - description: "invalid: no Trillian response: error", - err: fmt.Errorf("backend error"), - wantErr: true, - }, - { - description: "invalid: no Trillian response: nil", - wantErr: true, - }, - { - description: "invalid: no Trillian response: empty", - rsp: &trillian.QueueLeafResponse{}, - wantErr: true, - }, - { - description: "valid: gRPC status: duplicate", - rsp: testdata.DefaultTQlr(t, true), - }, - { - description: "valid: gRPC status: ok", - rsp: testdata.DefaultTQlr(t, false), - }, - } { - err := checkQueueLeaf(table.rsp, table.err) - if got, want := err != nil, table.wantErr; got != want { - t.Errorf("got error %v but wanted %v in test %q", got, want, table.description) - } - } -} - -func TestCheckGetLeavesByRange(t *testing.T) { - for _, table := range []struct { - description string - req *types.GetEntriesV1 - rsp *trillian.GetLeavesByRangeResponse - err error - wantErr bool - }{ - { - description: "invalid: no Trillian response: error", - req: &types.GetEntriesV1{Start: 0, End: 1}, - err: fmt.Errorf("backend error"), - wantErr: true, - }, - { - description: "invalid: no Trillian response: nil", - req: &types.GetEntriesV1{Start: 0, End: 1}, - wantErr: true, - }, - { - description: "invalid: bad Trillian response: no leaves", - req: &types.GetEntriesV1{Start: 0, End: 1}, - rsp: func(rsp *trillian.GetLeavesByRangeResponse) *trillian.GetLeavesByRangeResponse { - rsp.Leaves = nil - return rsp - }(testdata.DefaultTGlbrr(t, 0, 1)), - wantErr: true, - }, - { - description: "invalid: bad Trillian response: no signed log root", - req: &types.GetEntriesV1{Start: 0, End: 1}, - rsp: func(rsp *trillian.GetLeavesByRangeResponse) *trillian.GetLeavesByRangeResponse { - rsp.SignedLogRoot = nil - return rsp - }(testdata.DefaultTGlbrr(t, 0, 1)), - wantErr: true, - }, - { - description: "invalid: bad Trillian response: no log root", - req: &types.GetEntriesV1{Start: 0, End: 1}, - rsp: func(rsp *trillian.GetLeavesByRangeResponse) *trillian.GetLeavesByRangeResponse { - rsp.SignedLogRoot.LogRoot = nil - return rsp - }(testdata.DefaultTGlbrr(t, 0, 1)), - wantErr: true, - }, - { - description: "invalid: bad Trillian response: truncated log root", - req: &types.GetEntriesV1{Start: 0, End: 1}, - rsp: func(rsp *trillian.GetLeavesByRangeResponse) *trillian.GetLeavesByRangeResponse { - rsp.SignedLogRoot.LogRoot = rsp.SignedLogRoot.LogRoot[1:] - return rsp - }(testdata.DefaultTGlbrr(t, 0, 1)), - wantErr: true, - }, - { - description: "invalid: bad Trillian response: too many leaves", - req: &types.GetEntriesV1{Start: 0, End: 1}, - rsp: testdata.DefaultTGlbrr(t, 0, 2), - wantErr: true, - }, - { - description: "invalid: bad Trillian response: start is not a valid index", - req: &types.GetEntriesV1{Start: 10, End: 10}, - rsp: testdata.DefaultTGlbrr(t, 9, 9), - wantErr: true, - }, - { - description: "invalid: bad Trillian response: invalid leaf indices", - req: &types.GetEntriesV1{Start: 10, End: 11}, - rsp: testdata.DefaultTGlbrr(t, 11, 12), - wantErr: true, - }, - { - description: "valid", - req: &types.GetEntriesV1{Start: 10, End: 20}, - rsp: testdata.DefaultTGlbrr(t, 10, 20), - }, - } { - err := checkGetLeavesByRange(table.req, table.rsp, table.err) - if got, want := err != nil, table.wantErr; got != want { - t.Errorf("got error %v but wanted %v in test %q", got, want, table.description) - } - } -} - -func TestCheckGetInclusionProofByHash(t *testing.T) { - for _, table := range []struct { - description string - rsp *trillian.GetInclusionProofByHashResponse - err error - wantErr bool - }{ - { - description: "invalid: no Trillian response: error", - err: fmt.Errorf("backend failure"), - wantErr: true, - }, - { - description: "invalid: no Trillian response: nil", - wantErr: true, - }, - { - description: "invalid: bad Trillian response: no proofs", - rsp: &trillian.GetInclusionProofByHashResponse{}, - wantErr: true, - }, - { - description: "bad response: no proof", - rsp: func(rsp *trillian.GetInclusionProofByHashResponse) *trillian.GetInclusionProofByHashResponse { - rsp.Proof[0] = nil - return rsp - }(testdata.DefaultTGipbhr(t)), - wantErr: true, - }, - { - description: "bad response: proof with invalid node hash", - rsp: func(rsp *trillian.GetInclusionProofByHashResponse) *trillian.GetInclusionProofByHashResponse { - rsp.Proof[0].Hashes = append(rsp.Proof[0].Hashes, make([]byte, 0)) - return rsp - }(testdata.DefaultTGipbhr(t)), - wantErr: true, - }, - { - description: "valid", - rsp: testdata.DefaultTGipbhr(t), - }, - } { - err := checkGetInclusionProofByHash(newLogParameters(t, nil), table.rsp, table.err) - if got, want := err != nil, table.wantErr; got != want { - t.Errorf("got error %v but wanted %v in test %q", got, want, table.description) - } - } -} - -func TestCheckGetConsistencyProof(t *testing.T) { - for _, table := range []struct { - description string - rsp *trillian.GetConsistencyProofResponse - err error - wantErr bool - }{ - { - description: "invalid: no Trillian response: error", - err: fmt.Errorf("backend failure"), - wantErr: true, - }, - { - description: "invalid: no Trillian response: nil", - wantErr: true, - }, - { - description: "invalid: bad Trillian response: no proof", - rsp: &trillian.GetConsistencyProofResponse{}, - wantErr: true, - }, - { - description: "invalid: bad Trillian response: proof with invalid node hash", - rsp: func(rsp *trillian.GetConsistencyProofResponse) *trillian.GetConsistencyProofResponse { - rsp.Proof.Hashes = append(rsp.Proof.Hashes, make([]byte, 0)) - return rsp - }(testdata.DefaultTGcpr(t)), - wantErr: true, - }, - { - description: "valid", - rsp: testdata.DefaultTGcpr(t), - }, - } { - err := checkGetConsistencyProof(newLogParameters(t, nil), table.rsp, table.err) - if got, want := err != nil, table.wantErr; got != want { - t.Errorf("got error %v but wanted %v in test %q", got, want, table.description) - } - } -} - -func TestCheckGetLatestSignedLogRoot(t *testing.T) { - for _, table := range []struct { - description string - rsp *trillian.GetLatestSignedLogRootResponse - err error - wantErr bool - }{ - { - description: "invalid: no Trillian response: error", - err: fmt.Errorf("backend failure"), - wantErr: true, - }, - { - description: "invalid: no Trillian response: nil", - wantErr: true, - }, - { - description: "invalid: bad Trillian response: no signed log root", - rsp: func(rsp *trillian.GetLatestSignedLogRootResponse) *trillian.GetLatestSignedLogRootResponse { - rsp.SignedLogRoot = nil - return rsp - }(testdata.DefaultTSlr(t)), - wantErr: true, - }, - { - description: "invalid: bad Trillian response: no log root", - rsp: func(rsp *trillian.GetLatestSignedLogRootResponse) *trillian.GetLatestSignedLogRootResponse { - rsp.SignedLogRoot.LogRoot = nil - return rsp - }(testdata.DefaultTSlr(t)), - wantErr: true, - }, - { - description: "invalid: bad Trillian response: truncated log root", - rsp: func(rsp *trillian.GetLatestSignedLogRootResponse) *trillian.GetLatestSignedLogRootResponse { - rsp.SignedLogRoot.LogRoot = rsp.SignedLogRoot.LogRoot[1:] - return rsp - }(testdata.DefaultTSlr(t)), - wantErr: true, - }, - { - description: "invalid: bad Trillian response: truncated root hash", - rsp: testdata.Tslr(t, testdata.Tlr(t, testdata.TreeSize, testdata.Timestamp, make([]byte, 31))), - wantErr: true, - }, - { - description: "valid", - rsp: testdata.DefaultTSlr(t), - }, - } { - var lr ttypes.LogRootV1 - err := checkGetLatestSignedLogRoot(newLogParameters(t, nil), table.rsp, table.err, &lr) - if got, want := err != nil, table.wantErr; got != want { - t.Errorf("got error %v but wanted %v in test %q", got, want, table.description) - } - } -} diff --git a/types/ascii.go b/types/ascii.go deleted file mode 100644 index d27d79b..0000000 --- a/types/ascii.go +++ /dev/null @@ -1,421 +0,0 @@ -package types - -import ( - "bytes" - "encoding/hex" - "fmt" - "io" - "io/ioutil" - "strconv" -) - -const ( - // Delim is a key-value separator - Delim = "=" - - // EOL is a line sepator - EOL = "\n" - - // NumField* is the number of unique keys in an incoming ASCII message - NumFieldLeaf = 4 - NumFieldSignedTreeHead = 5 - NumFieldConsistencyProof = 3 - NumFieldInclusionProof = 3 - NumFieldLeavesRequest = 2 - NumFieldInclusionProofRequest = 2 - NumFieldConsistencyProofRequest = 2 - NumFieldLeafRequest = 5 - NumFieldCosignatureRequest = 2 - - // New leaf keys - ShardHint = "shard_hint" - Checksum = "checksum" - SignatureOverMessage = "signature_over_message" - VerificationKey = "verification_key" - DomainHint = "domain_hint" - - // Inclusion proof keys - LeafHash = "leaf_hash" - LeafIndex = "leaf_index" - InclusionPath = "inclusion_path" - - // Consistency proof keys - NewSize = "new_size" - OldSize = "old_size" - ConsistencyPath = "consistency_path" - - // Range of leaves keys - StartSize = "start_size" - EndSize = "end_size" - - // Tree head keys - Timestamp = "timestamp" - TreeSize = "tree_size" - RootHash = "root_hash" - - // Signature and signer-identity keys - Signature = "signature" - KeyHash = "key_hash" -) - -// MessageASCI is a wrapper that manages ASCII key-value pairs -type MessageASCII struct { - m map[string][]string -} - -// NewMessageASCII unpacks an incoming ASCII message -func NewMessageASCII(r io.Reader, numFieldExpected int) (*MessageASCII, error) { - buf, err := ioutil.ReadAll(r) - if err != nil { - return nil, fmt.Errorf("ReadAll: %v", err) - } - lines := bytes.Split(buf, []byte(EOL)) - if len(lines) <= 1 { - return nil, fmt.Errorf("Not enough lines: empty") - } - lines = lines[:len(lines)-1] // valid message => split gives empty last line - - msg := MessageASCII{make(map[string][]string)} - for _, line := range lines { - split := bytes.Index(line, []byte(Delim)) - if split == -1 { - return nil, fmt.Errorf("invalid line: %v", string(line)) - } - - key := string(line[:split]) - value := string(line[split+len(Delim):]) - values, ok := msg.m[key] - if !ok { - values = nil - msg.m[key] = values - } - msg.m[key] = append(values, value) - } - - if msg.NumField() != numFieldExpected { - return nil, fmt.Errorf("Unexpected number of keys: %v", msg.NumField()) - } - return &msg, nil -} - -// NumField returns the number of unique keys -func (msg *MessageASCII) NumField() int { - return len(msg.m) -} - -// GetStrings returns a list of strings -func (msg *MessageASCII) GetStrings(key string) []string { - strs, ok := msg.m[key] - if !ok { - return nil - } - return strs -} - -// GetString unpacks a string -func (msg *MessageASCII) GetString(key string) (string, error) { - strs := msg.GetStrings(key) - if len(strs) != 1 { - return "", fmt.Errorf("expected one string: %v", strs) - } - return strs[0], nil -} - -// GetUint64 unpacks an uint64 -func (msg *MessageASCII) GetUint64(key string) (uint64, error) { - str, err := msg.GetString(key) - if err != nil { - return 0, fmt.Errorf("GetString: %v", err) - } - num, err := strconv.ParseUint(str, 10, 64) - if err != nil { - return 0, fmt.Errorf("ParseUint: %v", err) - } - return num, nil -} - -// GetHash unpacks a hash -func (msg *MessageASCII) GetHash(key string) (*[HashSize]byte, error) { - str, err := msg.GetString(key) - if err != nil { - return nil, fmt.Errorf("GetString: %v", err) - } - - var hash [HashSize]byte - if err := decodeHex(str, hash[:]); err != nil { - return nil, fmt.Errorf("decodeHex: %v", err) - } - return &hash, nil -} - -// GetSignature unpacks a signature -func (msg *MessageASCII) GetSignature(key string) (*[SignatureSize]byte, error) { - str, err := msg.GetString(key) - if err != nil { - return nil, fmt.Errorf("GetString: %v", err) - } - - var signature [SignatureSize]byte - if err := decodeHex(str, signature[:]); err != nil { - return nil, fmt.Errorf("decodeHex: %v", err) - } - return &signature, nil -} - -// GetVerificationKey unpacks a verification key -func (msg *MessageASCII) GetVerificationKey(key string) (*[VerificationKeySize]byte, error) { - str, err := msg.GetString(key) - if err != nil { - return nil, fmt.Errorf("GetString: %v", err) - } - - var vk [VerificationKeySize]byte - if err := decodeHex(str, vk[:]); err != nil { - return nil, fmt.Errorf("decodeHex: %v", err) - } - return &vk, nil -} - -// decodeHex decodes a hex-encoded string into an already-sized byte slice -func decodeHex(str string, out []byte) error { - buf, err := hex.DecodeString(str) - if err != nil { - return fmt.Errorf("DecodeString: %v", err) - } - if len(buf) != len(out) { - return fmt.Errorf("invalid length: %v", len(buf)) - } - copy(out, buf) - return nil -} - -/* - * - * MarshalASCII wrappers for types that the log server outputs - * - */ -func (l *Leaf) MarshalASCII(w io.Writer) error { - if err := writeASCII(w, ShardHint, strconv.FormatUint(l.ShardHint, 10)); err != nil { - return fmt.Errorf("writeASCII: %v", err) - } - if err := writeASCII(w, Checksum, hex.EncodeToString(l.Checksum[:])); err != nil { - return fmt.Errorf("writeASCII: %v", err) - } - if err := writeASCII(w, SignatureOverMessage, hex.EncodeToString(l.Signature[:])); err != nil { - return fmt.Errorf("writeASCII: %v", err) - } - if err := writeASCII(w, KeyHash, hex.EncodeToString(l.KeyHash[:])); err != nil { - return fmt.Errorf("writeASCII: %v", err) - } - return nil -} - -func (sth *SignedTreeHead) MarshalASCII(w io.Writer) error { - if err := writeASCII(w, Timestamp, strconv.FormatUint(sth.Timestamp, 10)); err != nil { - return fmt.Errorf("writeASCII: %v", err) - } - if err := writeASCII(w, TreeSize, strconv.FormatUint(sth.TreeSize, 10)); err != nil { - return fmt.Errorf("writeASCII: %v", err) - } - if err := writeASCII(w, RootHash, hex.EncodeToString(sth.RootHash[:])); err != nil { - return fmt.Errorf("writeASCII: %v", err) - } - for _, sigident := range sth.SigIdent { - if err := sigident.MarshalASCII(w); err != nil { - return fmt.Errorf("MarshalASCII: %v", err) - } - } - return nil -} - -func (si *SigIdent) MarshalASCII(w io.Writer) error { - if err := writeASCII(w, Signature, hex.EncodeToString(si.Signature[:])); err != nil { - return fmt.Errorf("writeASCII: %v", err) - } - if err := writeASCII(w, KeyHash, hex.EncodeToString(si.KeyHash[:])); err != nil { - return fmt.Errorf("writeASCII: %v", err) - } - return nil -} - -func (p *ConsistencyProof) MarshalASCII(w io.Writer) error { - if err := writeASCII(w, NewSize, strconv.FormatUint(p.NewSize, 10)); err != nil { - return fmt.Errorf("writeASCII: %v", err) - } - if err := writeASCII(w, OldSize, strconv.FormatUint(p.OldSize, 10)); err != nil { - return fmt.Errorf("writeASCII: %v", err) - } - for _, hash := range p.Path { - if err := writeASCII(w, ConsistencyPath, hex.EncodeToString(hash[:])); err != nil { - return fmt.Errorf("writeASCII: %v", err) - } - } - return nil -} - -func (p *InclusionProof) MarshalASCII(w io.Writer) error { - if err := writeASCII(w, TreeSize, strconv.FormatUint(p.TreeSize, 10)); err != nil { - return fmt.Errorf("writeASCII: %v", err) - } - if err := writeASCII(w, LeafIndex, strconv.FormatUint(p.LeafIndex, 10)); err != nil { - return fmt.Errorf("writeASCII: %v", err) - } - for _, hash := range p.Path { - if err := writeASCII(w, InclusionPath, hex.EncodeToString(hash[:])); err != nil { - return fmt.Errorf("writeASCII: %v", err) - } - } - return nil -} - -func writeASCII(w io.Writer, key, value string) error { - if _, err := fmt.Fprintf(w, "%s%s%s%s", key, Delim, value, EOL); err != nil { - return fmt.Errorf("Fprintf: %v", err) - } - return nil -} - -/* - * - * Unmarshal ASCII wrappers that the log server and/or log clients receive. - * - */ -func (ll *LeafList) UnmarshalASCII(r io.Reader) error { - return nil -} - -func (sth *SignedTreeHead) UnmarshalASCII(r io.Reader) error { - msg, err := NewMessageASCII(r, NumFieldSignedTreeHead) - if err != nil { - return fmt.Errorf("NewMessageASCII: %v", err) - } - - // TreeHead - if sth.Timestamp, err = msg.GetUint64(Timestamp); err != nil { - return fmt.Errorf("GetUint64(Timestamp): %v", err) - } - if sth.TreeSize, err = msg.GetUint64(TreeSize); err != nil { - return fmt.Errorf("GetUint64(TreeSize): %v", err) - } - if sth.RootHash, err = msg.GetHash(RootHash); err != nil { - return fmt.Errorf("GetHash(RootHash): %v", err) - } - - // SigIdent - signatures := msg.GetStrings(Signature) - if len(signatures) == 0 { - return fmt.Errorf("no signer") - } - keyHashes := msg.GetStrings(KeyHash) - if len(signatures) != len(keyHashes) { - return fmt.Errorf("mismatched signature-signer count") - } - sth.SigIdent = make([]*SigIdent, 0, len(signatures)) - for i, n := 0, len(signatures); i < n; i++ { - var signature [SignatureSize]byte - if err := decodeHex(signatures[i], signature[:]); err != nil { - return fmt.Errorf("decodeHex: %v", err) - } - var hash [HashSize]byte - if err := decodeHex(keyHashes[i], hash[:]); err != nil { - return fmt.Errorf("decodeHex: %v", err) - } - sth.SigIdent = append(sth.SigIdent, &SigIdent{ - Signature: &signature, - KeyHash: &hash, - }) - } - return nil -} - -func (p *InclusionProof) UnmarshalASCII(r io.Reader) error { - return nil -} - -func (p *ConsistencyProof) UnmarshalASCII(r io.Reader) error { - return nil -} - -func (req *InclusionProofRequest) UnmarshalASCII(r io.Reader) error { - msg, err := NewMessageASCII(r, NumFieldInclusionProofRequest) - if err != nil { - return fmt.Errorf("NewMessageASCII: %v", err) - } - - if req.LeafHash, err = msg.GetHash(LeafHash); err != nil { - return fmt.Errorf("GetHash(LeafHash): %v", err) - } - if req.TreeSize, err = msg.GetUint64(TreeSize); err != nil { - return fmt.Errorf("GetUint64(TreeSize): %v", err) - } - return nil -} - -func (req *ConsistencyProofRequest) UnmarshalASCII(r io.Reader) error { - msg, err := NewMessageASCII(r, NumFieldConsistencyProofRequest) - if err != nil { - return fmt.Errorf("NewMessageASCII: %v", err) - } - - if req.NewSize, err = msg.GetUint64(NewSize); err != nil { - return fmt.Errorf("GetUint64(NewSize): %v", err) - } - if req.OldSize, err = msg.GetUint64(OldSize); err != nil { - return fmt.Errorf("GetUint64(OldSize): %v", err) - } - return nil -} - -func (req *LeavesRequest) UnmarshalASCII(r io.Reader) error { - msg, err := NewMessageASCII(r, NumFieldLeavesRequest) - if err != nil { - return fmt.Errorf("NewMessageASCII: %v", err) - } - - if req.StartSize, err = msg.GetUint64(StartSize); err != nil { - return fmt.Errorf("GetUint64(StartSize): %v", err) - } - if req.EndSize, err = msg.GetUint64(EndSize); err != nil { - return fmt.Errorf("GetUint64(EndSize): %v", err) - } - return nil -} - -func (req *LeafRequest) UnmarshalASCII(r io.Reader) error { - msg, err := NewMessageASCII(r, NumFieldLeafRequest) - if err != nil { - return fmt.Errorf("NewMessageASCII: %v", err) - } - - if req.ShardHint, err = msg.GetUint64(ShardHint); err != nil { - return fmt.Errorf("GetUint64(ShardHint): %v", err) - } - if req.Checksum, err = msg.GetHash(Checksum); err != nil { - return fmt.Errorf("GetHash(Checksum): %v", err) - } - if req.Signature, err = msg.GetSignature(SignatureOverMessage); err != nil { - return fmt.Errorf("GetSignature: %v", err) - } - if req.VerificationKey, err = msg.GetVerificationKey(VerificationKey); err != nil { - return fmt.Errorf("GetVerificationKey: %v", err) - } - if req.DomainHint, err = msg.GetString(DomainHint); err != nil { - return fmt.Errorf("GetString(DomainHint): %v", err) - } - return nil -} - -func (req *CosignatureRequest) UnmarshalASCII(r io.Reader) error { - msg, err := NewMessageASCII(r, NumFieldCosignatureRequest) - if err != nil { - return fmt.Errorf("NewMessageASCII: %v", err) - } - - if req.Signature, err = msg.GetSignature(Signature); err != nil { - return fmt.Errorf("GetSignature: %v", err) - } - if req.KeyHash, err = msg.GetHash(KeyHash); err != nil { - return fmt.Errorf("GetHash(KeyHash): %v", err) - } - return nil -} diff --git a/types/ascii_test.go b/types/ascii_test.go deleted file mode 100644 index 92732f9..0000000 --- a/types/ascii_test.go +++ /dev/null @@ -1,465 +0,0 @@ -package types - -import ( - "bytes" - "fmt" - "io" - "reflect" - "testing" -) - -/* - * - * MessageASCII methods and helpers - * - */ -func TestNewMessageASCII(t *testing.T) { - for _, table := range []struct { - description string - input io.Reader - wantErr bool - wantMap map[string][]string - }{ - { - description: "invalid: not enough lines", - input: bytes.NewBufferString(""), - wantErr: true, - }, - { - description: "invalid: lines must end with new line", - input: bytes.NewBufferString("k1=v1\nk2=v2"), - wantErr: true, - }, - { - description: "invalid: lines must not be empty", - input: bytes.NewBufferString("k1=v1\n\nk2=v2\n"), - wantErr: true, - }, - { - description: "invalid: wrong number of fields", - input: bytes.NewBufferString("k1=v1\n"), - wantErr: true, - }, - { - description: "valid", - input: bytes.NewBufferString("k1=v1\nk2=v2\nk2=v3=4\n"), - wantMap: map[string][]string{ - "k1": []string{"v1"}, - "k2": []string{"v2", "v3=4"}, - }, - }, - } { - msg, err := NewMessageASCII(table.input, len(table.wantMap)) - if got, want := err != nil, table.wantErr; got != want { - t.Errorf("got error %v but wanted %v in test %q: %v", got, want, table.description, err) - } - if err != nil { - continue - } - if got, want := msg.m, table.wantMap; !reflect.DeepEqual(got, want) { - t.Errorf("got\n\t%v\nbut wanted\n\t%v\nin test %q", got, want, table.description) - } - } -} - -func TestNumField(t *testing.T) {} -func TestGetStrings(t *testing.T) {} -func TestGetString(t *testing.T) {} -func TestGetUint64(t *testing.T) {} -func TestGetHash(t *testing.T) {} -func TestGetSignature(t *testing.T) {} -func TestGetVerificationKey(t *testing.T) {} -func TestDecodeHex(t *testing.T) {} - -/* - * - * MarshalASCII methods and helpers - * - */ -func TestLeafMarshalASCII(t *testing.T) { - description := "valid: two leaves" - leafList := []*Leaf{ - &Leaf{ - Message: Message{ - ShardHint: 123, - Checksum: testBuffer32, - }, - SigIdent: SigIdent{ - Signature: testBuffer64, - KeyHash: testBuffer32, - }, - }, - &Leaf{ - Message: Message{ - ShardHint: 456, - Checksum: testBuffer32, - }, - SigIdent: SigIdent{ - Signature: testBuffer64, - KeyHash: testBuffer32, - }, - }, - } - wantBuf := bytes.NewBufferString(fmt.Sprintf( - "%s%s%d%s"+"%s%s%x%s"+"%s%s%x%s"+"%s%s%x%s"+ - "%s%s%d%s"+"%s%s%x%s"+"%s%s%x%s"+"%s%s%x%s", - // Leaf 1 - ShardHint, Delim, 123, EOL, - Checksum, Delim, testBuffer32[:], EOL, - SignatureOverMessage, Delim, testBuffer64[:], EOL, - KeyHash, Delim, testBuffer32[:], EOL, - // Leaf 2 - ShardHint, Delim, 456, EOL, - Checksum, Delim, testBuffer32[:], EOL, - SignatureOverMessage, Delim, testBuffer64[:], EOL, - KeyHash, Delim, testBuffer32[:], EOL, - )) - buf := bytes.NewBuffer(nil) - for _, leaf := range leafList { - if err := leaf.MarshalASCII(buf); err != nil { - t.Errorf("expected error %v but got %v in test %q: %v", false, true, description, err) - return - } - } - if got, want := buf.Bytes(), wantBuf.Bytes(); !bytes.Equal(got, want) { - t.Errorf("got\n\t%v\nbut wanted\n\t%v\nin test %q", string(got), string(want), description) - } -} - -func TestSignedTreeHeadMarshalASCII(t *testing.T) { - description := "valid" - sth := &SignedTreeHead{ - TreeHead: TreeHead{ - Timestamp: 123, - TreeSize: 456, - RootHash: testBuffer32, - }, - SigIdent: []*SigIdent{ - &SigIdent{ - Signature: testBuffer64, - KeyHash: testBuffer32, - }, - &SigIdent{ - Signature: testBuffer64, - KeyHash: testBuffer32, - }, - }, - } - wantBuf := bytes.NewBufferString(fmt.Sprintf( - "%s%s%d%s"+"%s%s%d%s"+"%s%s%x%s"+"%s%s%x%s"+"%s%s%x%s"+"%s%s%x%s"+"%s%s%x%s", - Timestamp, Delim, 123, EOL, - TreeSize, Delim, 456, EOL, - RootHash, Delim, testBuffer32[:], EOL, - Signature, Delim, testBuffer64[:], EOL, - KeyHash, Delim, testBuffer32[:], EOL, - Signature, Delim, testBuffer64[:], EOL, - KeyHash, Delim, testBuffer32[:], EOL, - )) - buf := bytes.NewBuffer(nil) - if err := sth.MarshalASCII(buf); err != nil { - t.Errorf("expected error %v but got %v in test %q", false, true, description) - return - } - if got, want := buf.Bytes(), wantBuf.Bytes(); !bytes.Equal(got, want) { - t.Errorf("got\n\t%v\nbut wanted\n\t%v\nin test %q", string(got), string(want), description) - } -} - -func TestInclusionProofMarshalASCII(t *testing.T) { - description := "valid" - proof := InclusionProof{ - TreeSize: 321, - LeafIndex: 123, - Path: []*[HashSize]byte{ - testBuffer32, - testBuffer32, - }, - } - wantBuf := bytes.NewBufferString(fmt.Sprintf( - "%s%s%d%s"+"%s%s%d%s"+"%s%s%x%s"+"%s%s%x%s", - TreeSize, Delim, 321, EOL, - LeafIndex, Delim, 123, EOL, - InclusionPath, Delim, testBuffer32[:], EOL, - InclusionPath, Delim, testBuffer32[:], EOL, - )) - buf := bytes.NewBuffer(nil) - if err := proof.MarshalASCII(buf); err != nil { - t.Errorf("expected error %v but got %v in test %q", false, true, description) - return - } - if got, want := buf.Bytes(), wantBuf.Bytes(); !bytes.Equal(got, want) { - t.Errorf("got\n\t%v\nbut wanted\n\t%v\nin test %q", string(got), string(want), description) - } -} - -func TestConsistencyProofMarshalASCII(t *testing.T) { - description := "valid" - proof := ConsistencyProof{ - NewSize: 321, - OldSize: 123, - Path: []*[HashSize]byte{ - testBuffer32, - testBuffer32, - }, - } - wantBuf := bytes.NewBufferString(fmt.Sprintf( - "%s%s%d%s"+"%s%s%d%s"+"%s%s%x%s"+"%s%s%x%s", - NewSize, Delim, 321, EOL, - OldSize, Delim, 123, EOL, - ConsistencyPath, Delim, testBuffer32[:], EOL, - ConsistencyPath, Delim, testBuffer32[:], EOL, - )) - buf := bytes.NewBuffer(nil) - if err := proof.MarshalASCII(buf); err != nil { - t.Errorf("expected error %v but got %v in test %q", false, true, description) - return - } - if got, want := buf.Bytes(), wantBuf.Bytes(); !bytes.Equal(got, want) { - t.Errorf("got\n\t%v\nbut wanted\n\t%v\nin test %q", string(got), string(want), description) - } -} - -func TestWriteASCII(t *testing.T) { -} - -/* - * - * UnmarshalASCII methods and helpers - * - */ -func TestLeafListUnmarshalASCII(t *testing.T) {} - -func TestSignedTreeHeadUnmarshalASCII(t *testing.T) { - for _, table := range []struct { - description string - buf io.Reader - wantErr bool - wantSth *SignedTreeHead - }{ - { - description: "valid", - buf: bytes.NewBufferString(fmt.Sprintf( - "%s%s%d%s"+"%s%s%d%s"+"%s%s%x%s"+"%s%s%x%s"+"%s%s%x%s"+"%s%s%x%s"+"%s%s%x%s", - Timestamp, Delim, 123, EOL, - TreeSize, Delim, 456, EOL, - RootHash, Delim, testBuffer32[:], EOL, - Signature, Delim, testBuffer64[:], EOL, - KeyHash, Delim, testBuffer32[:], EOL, - Signature, Delim, testBuffer64[:], EOL, - KeyHash, Delim, testBuffer32[:], EOL, - )), - wantSth: &SignedTreeHead{ - TreeHead: TreeHead{ - Timestamp: 123, - TreeSize: 456, - RootHash: testBuffer32, - }, - SigIdent: []*SigIdent{ - &SigIdent{ - Signature: testBuffer64, - KeyHash: testBuffer32, - }, - &SigIdent{ - Signature: testBuffer64, - KeyHash: testBuffer32, - }, - }, - }, - }, - } { - var sth SignedTreeHead - err := sth.UnmarshalASCII(table.buf) - if got, want := err != nil, table.wantErr; got != want { - t.Errorf("got error %v but wanted %v in test %q: %v", got, want, table.description, err) - } - if err != nil { - continue - } - if got, want := &sth, table.wantSth; !reflect.DeepEqual(got, want) { - t.Errorf("got\n\t%v\nbut wanted\n\t%v\nin test %q", got, want, table.description) - } - } -} - -func TestInclusionProofUnmarshalASCII(t *testing.T) {} -func TestConsistencyProofUnmarshalASCII(t *testing.T) {} - -func TestInclusionProofRequestUnmarshalASCII(t *testing.T) { - for _, table := range []struct { - description string - buf io.Reader - wantErr bool - wantReq *InclusionProofRequest - }{ - { - description: "valid", - buf: bytes.NewBufferString(fmt.Sprintf( - "%s%s%x%s"+"%s%s%d%s", - LeafHash, Delim, testBuffer32[:], EOL, - TreeSize, Delim, 123, EOL, - )), - wantReq: &InclusionProofRequest{ - LeafHash: testBuffer32, - TreeSize: 123, - }, - }, - } { - var req InclusionProofRequest - err := req.UnmarshalASCII(table.buf) - if got, want := err != nil, table.wantErr; got != want { - t.Errorf("got error %v but wanted %v in test %q: %v", got, want, table.description, err) - } - if err != nil { - continue - } - if got, want := &req, table.wantReq; !reflect.DeepEqual(got, want) { - t.Errorf("got\n\t%v\nbut wanted\n\t%v\nin test %q", got, want, table.description) - } - } -} - -func TestConsistencyProofRequestUnmarshalASCII(t *testing.T) { - for _, table := range []struct { - description string - buf io.Reader - wantErr bool - wantReq *ConsistencyProofRequest - }{ - { - description: "valid", - buf: bytes.NewBufferString(fmt.Sprintf( - "%s%s%d%s"+"%s%s%d%s", - NewSize, Delim, 321, EOL, - OldSize, Delim, 123, EOL, - )), - wantReq: &ConsistencyProofRequest{ - NewSize: 321, - OldSize: 123, - }, - }, - } { - var req ConsistencyProofRequest - err := req.UnmarshalASCII(table.buf) - if got, want := err != nil, table.wantErr; got != want { - t.Errorf("got error %v but wanted %v in test %q: %v", got, want, table.description, err) - } - if err != nil { - continue - } - if got, want := &req, table.wantReq; !reflect.DeepEqual(got, want) { - t.Errorf("got\n\t%v\nbut wanted\n\t%v\nin test %q", got, want, table.description) - } - } -} - -func TestLeavesRequestUnmarshalASCII(t *testing.T) { - for _, table := range []struct { - description string - buf io.Reader - wantErr bool - wantReq *LeavesRequest - }{ - { - description: "valid", - buf: bytes.NewBufferString(fmt.Sprintf( - "%s%s%d%s"+"%s%s%d%s", - StartSize, Delim, 123, EOL, - EndSize, Delim, 456, EOL, - )), - wantReq: &LeavesRequest{ - StartSize: 123, - EndSize: 456, - }, - }, - } { - var req LeavesRequest - err := req.UnmarshalASCII(table.buf) - if got, want := err != nil, table.wantErr; got != want { - t.Errorf("got error %v but wanted %v in test %q: %v", got, want, table.description, err) - } - if err != nil { - continue - } - if got, want := &req, table.wantReq; !reflect.DeepEqual(got, want) { - t.Errorf("got\n\t%v\nbut wanted\n\t%v\nin test %q", got, want, table.description) - } - } -} - -func TestLeafRequestUnmarshalASCII(t *testing.T) { - for _, table := range []struct { - description string - buf io.Reader - wantErr bool - wantReq *LeafRequest - }{ - { - description: "valid", - buf: bytes.NewBufferString(fmt.Sprintf( - "%s%s%d%s"+"%s%s%x%s"+"%s%s%x%s"+"%s%s%x%s"+"%s%s%s%s", - ShardHint, Delim, 123, EOL, - Checksum, Delim, testBuffer32[:], EOL, - SignatureOverMessage, Delim, testBuffer64[:], EOL, - VerificationKey, Delim, testBuffer32[:], EOL, - DomainHint, Delim, "example.com", EOL, - )), - wantReq: &LeafRequest{ - Message: Message{ - ShardHint: 123, - Checksum: testBuffer32, - }, - Signature: testBuffer64, - VerificationKey: testBuffer32, - DomainHint: "example.com", - }, - }, - } { - var req LeafRequest - err := req.UnmarshalASCII(table.buf) - if got, want := err != nil, table.wantErr; got != want { - t.Errorf("got error %v but wanted %v in test %q: %v", got, want, table.description, err) - } - if err != nil { - continue - } - if got, want := &req, table.wantReq; !reflect.DeepEqual(got, want) { - t.Errorf("got\n\t%v\nbut wanted\n\t%v\nin test %q", got, want, table.description) - } - } -} - -func TestCosignatureRequestUnmarshalASCII(t *testing.T) { - for _, table := range []struct { - description string - buf io.Reader - wantErr bool - wantReq *CosignatureRequest - }{ - { - description: "valid", - buf: bytes.NewBufferString(fmt.Sprintf( - "%s%s%x%s"+"%s%s%x%s", - Signature, Delim, testBuffer64[:], EOL, - KeyHash, Delim, testBuffer32[:], EOL, - )), - wantReq: &CosignatureRequest{ - SigIdent: SigIdent{ - Signature: testBuffer64, - KeyHash: testBuffer32, - }, - }, - }, - } { - var req CosignatureRequest - err := req.UnmarshalASCII(table.buf) - if got, want := err != nil, table.wantErr; got != want { - t.Errorf("got error %v but wanted %v in test %q: %v", got, want, table.description, err) - } - if err != nil { - continue - } - if got, want := &req, table.wantReq; !reflect.DeepEqual(got, want) { - t.Errorf("got\n\t%v\nbut wanted\n\t%v\nin test %q", got, want, table.description) - } - } -} diff --git a/types/trunnel.go b/types/trunnel.go deleted file mode 100644 index 268f6f7..0000000 --- a/types/trunnel.go +++ /dev/null @@ -1,60 +0,0 @@ -package types - -import ( - "encoding/binary" - "fmt" -) - -const ( - // MessageSize is the number of bytes in a Trunnel-encoded leaf message - MessageSize = 8 + HashSize - // LeafSize is the number of bytes in a Trunnel-encoded leaf - LeafSize = MessageSize + SignatureSize + HashSize -) - -// Marshal returns a Trunnel-encoded message -func (m *Message) Marshal() []byte { - buf := make([]byte, MessageSize) - binary.BigEndian.PutUint64(buf, m.ShardHint) - copy(buf[8:], m.Checksum[:]) - return buf -} - -// Marshal returns a Trunnel-encoded leaf -func (l *Leaf) Marshal() []byte { - buf := l.Message.Marshal() - buf = append(buf, l.SigIdent.Signature[:]...) - buf = append(buf, l.SigIdent.KeyHash[:]...) - return buf -} - -// Marshal returns a Trunnel-encoded tree head -func (th *TreeHead) Marshal() []byte { - buf := make([]byte, 8+8+HashSize) - binary.BigEndian.PutUint64(buf[0:8], th.Timestamp) - binary.BigEndian.PutUint64(buf[8:16], th.TreeSize) - copy(buf[16:], th.RootHash[:]) - return buf -} - -// Unmarshal parses the Trunnel-encoded buffer as a leaf -func (l *Leaf) Unmarshal(buf []byte) error { - if len(buf) != LeafSize { - return fmt.Errorf("invalid leaf size: %v", len(buf)) - } - // Shard hint - l.ShardHint = binary.BigEndian.Uint64(buf) - offset := 8 - // Checksum - l.Checksum = &[HashSize]byte{} - copy(l.Checksum[:], buf[offset:offset+HashSize]) - offset += HashSize - // Signature - l.Signature = &[SignatureSize]byte{} - copy(l.Signature[:], buf[offset:offset+SignatureSize]) - offset += SignatureSize - // KeyHash - l.KeyHash = &[HashSize]byte{} - copy(l.KeyHash[:], buf[offset:]) - return nil -} diff --git a/types/trunnel_test.go b/types/trunnel_test.go deleted file mode 100644 index 297578c..0000000 --- a/types/trunnel_test.go +++ /dev/null @@ -1,114 +0,0 @@ -package types - -import ( - "bytes" - "reflect" - "testing" -) - -var ( - testBuffer32 = &[32]byte{0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31} - testBuffer64 = &[64]byte{0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, 62, 63} -) - -func TestMarshalMessage(t *testing.T) { - description := "valid: shard hint 72623859790382856, checksum 0x00,0x01,..." - message := &Message{ - ShardHint: 72623859790382856, - Checksum: testBuffer32, - } - want := bytes.Join([][]byte{ - []byte{0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08}, - testBuffer32[:], - }, nil) - if got := message.Marshal(); !bytes.Equal(got, want) { - t.Errorf("got message\n\t%v\nbut wanted\n\t%v\nin test %q\n", got, want, description) - } -} - -func TestMarshalLeaf(t *testing.T) { - description := "valid: shard hint 72623859790382856, buffers 0x00,0x01,..." - leaf := &Leaf{ - Message: Message{ - ShardHint: 72623859790382856, - Checksum: testBuffer32, - }, - SigIdent: SigIdent{ - Signature: testBuffer64, - KeyHash: testBuffer32, - }, - } - want := bytes.Join([][]byte{ - []byte{0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08}, - testBuffer32[:], testBuffer64[:], testBuffer32[:], - }, nil) - if got := leaf.Marshal(); !bytes.Equal(got, want) { - t.Errorf("got leaf\n\t%v\nbut wanted\n\t%v\nin test %q\n", got, want, description) - } -} - -func TestMarshalTreeHead(t *testing.T) { - description := "valid: timestamp 16909060, tree size 72623859790382856, root hash 0x00,0x01,..." - th := &TreeHead{ - Timestamp: 16909060, - TreeSize: 72623859790382856, - RootHash: testBuffer32, - } - want := bytes.Join([][]byte{ - []byte{0x00, 0x00, 0x00, 0x00, 0x01, 0x02, 0x03, 0x04}, - []byte{0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08}, - testBuffer32[:], - }, nil) - if got := th.Marshal(); !bytes.Equal(got, want) { - t.Errorf("got tree head\n\t%v\nbut wanted\n\t%v\nin test %q\n", got, want, description) - } -} - -func TestUnmarshalLeaf(t *testing.T) { - for _, table := range []struct { - description string - serialized []byte - wantErr bool - want *Leaf - }{ - { - description: "invalid: not enough bytes", - serialized: make([]byte, LeafSize-1), - wantErr: true, - }, - { - description: "invalid: too many bytes", - serialized: make([]byte, LeafSize+1), - wantErr: true, - }, - { - description: "valid: shard hint 72623859790382856, buffers 0x00,0x01,...", - serialized: bytes.Join([][]byte{ - []byte{0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08}, - testBuffer32[:], testBuffer64[:], testBuffer32[:], - }, nil), - want: &Leaf{ - Message: Message{ - ShardHint: 72623859790382856, - Checksum: testBuffer32, - }, - SigIdent: SigIdent{ - Signature: testBuffer64, - KeyHash: testBuffer32, - }, - }, - }, - } { - var leaf Leaf - err := leaf.Unmarshal(table.serialized) - if got, want := err != nil, table.wantErr; got != want { - t.Errorf("got error %v but wanted %v in test %q: %v", got, want, table.description, err) - } - if err != nil { - continue - } - if got, want := &leaf, table.want; !reflect.DeepEqual(got, want) { - t.Errorf("got leaf\n\t%v\nbut wanted\n\t%v\nin test %q\n", got, want, table.description) - } - } -} diff --git a/types/types.go b/types/types.go deleted file mode 100644 index 9ca7db8..0000000 --- a/types/types.go +++ /dev/null @@ -1,155 +0,0 @@ -package types - -import ( - "crypto" - "crypto/ed25519" - "crypto/sha256" - "fmt" - "strings" -) - -const ( - HashSize = sha256.Size - SignatureSize = ed25519.SignatureSize - VerificationKeySize = ed25519.PublicKeySize - - EndpointAddLeaf = Endpoint("add-leaf") - EndpointAddCosignature = Endpoint("add-cosignature") - EndpointGetTreeHeadLatest = Endpoint("get-tree-head-latest") - EndpointGetTreeHeadToSign = Endpoint("get-tree-head-to-sign") - EndpointGetTreeHeadCosigned = Endpoint("get-tree-head-cosigned") - EndpointGetProofByHash = Endpoint("get-proof-by-hash") - EndpointGetConsistencyProof = Endpoint("get-consistency-proof") - EndpointGetLeaves = Endpoint("get-leaves") -) - -// Endpoint is a named HTTP API endpoint -type Endpoint string - -// Path joins a number of components to form a full endpoint path. For example, -// EndpointAddLeaf.Path("example.com", "st/v0") -> example.com/st/v0/add-leaf. -func (e Endpoint) Path(components ...string) string { - return strings.Join(append(components, string(e)), "/") -} - -// Leaf is the log's Merkle tree leaf. -type Leaf struct { - Message - SigIdent -} - -// Message is composed of a shard hint and a checksum. The submitter selects -// these values to fit the log's shard interval and the opaque data in question. -type Message struct { - ShardHint uint64 - Checksum *[HashSize]byte -} - -// SigIdent is composed of a signature-signer pair. The signature is computed -// over the Trunnel-serialized leaf message. KeyHash identifies the signer. -type SigIdent struct { - Signature *[SignatureSize]byte - KeyHash *[HashSize]byte -} - -// SignedTreeHead is composed of a tree head and a list of signature-signer -// pairs. Each signature is computed over the Trunnel-serialized tree head. -type SignedTreeHead struct { - TreeHead - SigIdent []*SigIdent -} - -// TreeHead is the log's tree head. -type TreeHead struct { - Timestamp uint64 - TreeSize uint64 - RootHash *[HashSize]byte -} - -// ConsistencyProof is a consistency proof that proves the log's append-only -// property. -type ConsistencyProof struct { - NewSize uint64 - OldSize uint64 - Path []*[HashSize]byte -} - -// InclusionProof is an inclusion proof that proves a leaf is included in the -// log. -type InclusionProof struct { - TreeSize uint64 - LeafIndex uint64 - Path []*[HashSize]byte -} - -// LeafList is a list of leaves -type LeafList []*Leaf - -// ConsistencyProofRequest is a get-consistency-proof request -type ConsistencyProofRequest struct { - NewSize uint64 - OldSize uint64 -} - -// InclusionProofRequest is a get-proof-by-hash request -type InclusionProofRequest struct { - LeafHash *[HashSize]byte - TreeSize uint64 -} - -// LeavesRequest is a get-leaves request -type LeavesRequest struct { - StartSize uint64 - EndSize uint64 -} - -// LeafRequest is an add-leaf request -type LeafRequest struct { - Message - Signature *[SignatureSize]byte - VerificationKey *[VerificationKeySize]byte - DomainHint string -} - -// CosignatureRequest is an add-cosignature request -type CosignatureRequest struct { - SigIdent -} - -// Sign signs the tree head using the log's signature scheme -func (th *TreeHead) Sign(signer crypto.Signer) (*SignedTreeHead, error) { - sig, err := signer.Sign(nil, th.Marshal(), crypto.Hash(0)) - if err != nil { - return nil, fmt.Errorf("Sign: %v", err) - } - - sigident := SigIdent{ - KeyHash: Hash(signer.Public().(ed25519.PublicKey)[:]), - Signature: &[SignatureSize]byte{}, - } - copy(sigident.Signature[:], sig) - return &SignedTreeHead{ - TreeHead: *th, - SigIdent: []*SigIdent{ - &sigident, - }, - }, nil -} - -// Verify verifies the tree head signature using the log's signature scheme -func (th *TreeHead) Verify(vk *[VerificationKeySize]byte, sig *[SignatureSize]byte) error { - if !ed25519.Verify(ed25519.PublicKey(vk[:]), th.Marshal(), sig[:]) { - return fmt.Errorf("invalid tree head signature") - } - return nil -} - -// Verify checks if a leaf is included in the log -func (p *InclusionProof) Verify(leaf *Leaf, th *TreeHead) error { // TODO - return nil -} - -// Verify checks if two tree heads are consistent -func (p *ConsistencyProof) Verify(oldTH, newTH *TreeHead) error { // TODO - return nil -} diff --git a/types/types_test.go b/types/types_test.go deleted file mode 100644 index da89c59..0000000 --- a/types/types_test.go +++ /dev/null @@ -1,58 +0,0 @@ -package types - -import ( - "testing" -) - -func TestEndpointPath(t *testing.T) { - base, prefix, proto := "example.com", "log", "st/v0" - for _, table := range []struct { - endpoint Endpoint - want string - }{ - { - endpoint: EndpointAddLeaf, - want: "example.com/log/st/v0/add-leaf", - }, - { - endpoint: EndpointAddCosignature, - want: "example.com/log/st/v0/add-cosignature", - }, - { - endpoint: EndpointGetTreeHeadLatest, - want: "example.com/log/st/v0/get-tree-head-latest", - }, - { - endpoint: EndpointGetTreeHeadToSign, - want: "example.com/log/st/v0/get-tree-head-to-sign", - }, - { - endpoint: EndpointGetTreeHeadCosigned, - want: "example.com/log/st/v0/get-tree-head-cosigned", - }, - { - endpoint: EndpointGetConsistencyProof, - want: "example.com/log/st/v0/get-consistency-proof", - }, - { - endpoint: EndpointGetProofByHash, - want: "example.com/log/st/v0/get-proof-by-hash", - }, - { - endpoint: EndpointGetLeaves, - want: "example.com/log/st/v0/get-leaves", - }, - } { - if got, want := table.endpoint.Path(base+"/"+prefix+"/"+proto), table.want; got != want { - t.Errorf("got endpoint\n%s\n\tbut wanted\n%s\n\twith one component", got, want) - } - if got, want := table.endpoint.Path(base, prefix, proto), table.want; got != want { - t.Errorf("got endpoint\n%s\n\tbut wanted\n%s\n\tmultiple components", got, want) - } - } -} - -func TestTreeHeadSign(t *testing.T) {} -func TestTreeHeadVerify(t *testing.T) {} -func TestInclusionProofVerify(t *testing.T) {} -func TestConsistencyProofVerify(t *testing.T) {} diff --git a/types/util.go b/types/util.go deleted file mode 100644 index 3cd7dfa..0000000 --- a/types/util.go +++ /dev/null @@ -1,21 +0,0 @@ -package types - -import ( - "crypto/sha256" -) - -const ( - LeafHashPrefix = 0x00 -) - -func Hash(buf []byte) *[HashSize]byte { - var ret [HashSize]byte - hash := sha256.New() - hash.Write(buf) - copy(ret[:], hash.Sum(nil)) - return &ret -} - -func HashLeaf(buf []byte) *[HashSize]byte { - return Hash(append([]byte{LeafHashPrefix}, buf...)) -} diff --git a/util.go b/util.go deleted file mode 100644 index a8c918e..0000000 --- a/util.go +++ /dev/null @@ -1,27 +0,0 @@ -package stfe - -import ( - ttypes "github.com/google/trillian/types" - "github.com/system-transparency/stfe/types" -) - -func NewTreeHeadFromLogRoot(lr *ttypes.LogRootV1) *types.TreeHead { - var hash [types.HashSize]byte - th := types.TreeHead{ - Timestamp: uint64(lr.TimestampNanos / 1000 / 1000 / 1000), - TreeSize: uint64(lr.TreeSize), - RootHash: &hash, - } - copy(th.RootHash[:], lr.RootHash) - return &th -} - -func NodePathFromHashes(hashes [][]byte) []*[types.HashSize]byte { - var path []*[types.HashSize]byte - for _, hash := range hashes { - var h [types.HashSize]byte - copy(h[:], hash) - path = append(path, &h) - } - return path -} diff --git a/util_test.go b/util_test.go deleted file mode 100644 index b40a672..0000000 --- a/util_test.go +++ /dev/null @@ -1,17 +0,0 @@ -package stfe - -import ( - "testing" -) - -// TODO: TestNewTreeHeadV1FromLogRoot -func TestNewTreeHeadV1FromLogRoot(t *testing.T) { -} - -// TODO: TestNewNodePathFromHashPath -func TestNewNodePathFromHashPath(t *testing.T) { -} - -// TODO: TestStItemListFromLeaves -func TestStItemListFromLeaves(t *testing.T) { -} -- cgit v1.2.3 From 6782bd01db5a443bb6594ac6d577cfe95e2a427a Mon Sep 17 00:00:00 2001 From: Rasmus Dahlberg Date: Mon, 7 Jun 2021 00:04:51 +0200 Subject: generated state manager mock --- pkg/mocks/state.go | 107 +++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 107 insertions(+) create mode 100644 pkg/mocks/state.go diff --git a/pkg/mocks/state.go b/pkg/mocks/state.go new file mode 100644 index 0000000..41d8d08 --- /dev/null +++ b/pkg/mocks/state.go @@ -0,0 +1,107 @@ +// Code generated by MockGen. DO NOT EDIT. +// Source: github.com/system-transparency/stfe/pkg/state (interfaces: StateManager) + +// Package mocks is a generated GoMock package. +package mocks + +import ( + context "context" + reflect "reflect" + + gomock "github.com/golang/mock/gomock" + types "github.com/system-transparency/stfe/pkg/types" +) + +// MockStateManager is a mock of StateManager interface. +type MockStateManager struct { + ctrl *gomock.Controller + recorder *MockStateManagerMockRecorder +} + +// MockStateManagerMockRecorder is the mock recorder for MockStateManager. +type MockStateManagerMockRecorder struct { + mock *MockStateManager +} + +// NewMockStateManager creates a new mock instance. +func NewMockStateManager(ctrl *gomock.Controller) *MockStateManager { + mock := &MockStateManager{ctrl: ctrl} + mock.recorder = &MockStateManagerMockRecorder{mock} + return mock +} + +// EXPECT returns an object that allows the caller to indicate expected use. +func (m *MockStateManager) EXPECT() *MockStateManagerMockRecorder { + return m.recorder +} + +// AddCosignature mocks base method. +func (m *MockStateManager) AddCosignature(arg0 context.Context, arg1 *[32]byte, arg2 *[64]byte) error { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "AddCosignature", arg0, arg1, arg2) + ret0, _ := ret[0].(error) + return ret0 +} + +// AddCosignature indicates an expected call of AddCosignature. +func (mr *MockStateManagerMockRecorder) AddCosignature(arg0, arg1, arg2 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "AddCosignature", reflect.TypeOf((*MockStateManager)(nil).AddCosignature), arg0, arg1, arg2) +} + +// Cosigned mocks base method. +func (m *MockStateManager) Cosigned(arg0 context.Context) (*types.SignedTreeHead, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "Cosigned", arg0) + ret0, _ := ret[0].(*types.SignedTreeHead) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// Cosigned indicates an expected call of Cosigned. +func (mr *MockStateManagerMockRecorder) Cosigned(arg0 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Cosigned", reflect.TypeOf((*MockStateManager)(nil).Cosigned), arg0) +} + +// Latest mocks base method. +func (m *MockStateManager) Latest(arg0 context.Context) (*types.SignedTreeHead, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "Latest", arg0) + ret0, _ := ret[0].(*types.SignedTreeHead) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// Latest indicates an expected call of Latest. +func (mr *MockStateManagerMockRecorder) Latest(arg0 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Latest", reflect.TypeOf((*MockStateManager)(nil).Latest), arg0) +} + +// Run mocks base method. +func (m *MockStateManager) Run(arg0 context.Context) { + m.ctrl.T.Helper() + m.ctrl.Call(m, "Run", arg0) +} + +// Run indicates an expected call of Run. +func (mr *MockStateManagerMockRecorder) Run(arg0 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Run", reflect.TypeOf((*MockStateManager)(nil).Run), arg0) +} + +// ToSign mocks base method. +func (m *MockStateManager) ToSign(arg0 context.Context) (*types.SignedTreeHead, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "ToSign", arg0) + ret0, _ := ret[0].(*types.SignedTreeHead) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// ToSign indicates an expected call of ToSign. +func (mr *MockStateManagerMockRecorder) ToSign(arg0 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "ToSign", reflect.TypeOf((*MockStateManager)(nil).ToSign), arg0) +} -- cgit v1.2.3 From 345fe658fa8a4306caa74f72a618e499343675c2 Mon Sep 17 00:00:00 2001 From: Rasmus Dahlberg Date: Mon, 7 Jun 2021 00:09:57 +0200 Subject: added start on refactored instance tests --- pkg/instance/endpoint_test.go | 588 +++++++++++++++++++----------------------- pkg/instance/instance.go | 69 +++++ pkg/instance/instance_test.go | 155 +---------- pkg/instance/metric.go | 10 +- pkg/instance/request.go | 77 ------ pkg/instance/request_test.go | 318 ----------------------- 6 files changed, 345 insertions(+), 872 deletions(-) delete mode 100644 pkg/instance/request.go delete mode 100644 pkg/instance/request_test.go diff --git a/pkg/instance/endpoint_test.go b/pkg/instance/endpoint_test.go index 8511b8d..efcd4c0 100644 --- a/pkg/instance/endpoint_test.go +++ b/pkg/instance/endpoint_test.go @@ -2,479 +2,431 @@ package stfe import ( "bytes" - "context" + "encoding/hex" "fmt" + "io" "net/http" "net/http/httptest" - "reflect" "testing" "github.com/golang/mock/gomock" - cttestdata "github.com/google/certificate-transparency-go/trillian/testdata" - "github.com/google/trillian" - "github.com/system-transparency/stfe/pkg/testdata" + "github.com/system-transparency/stfe/pkg/mocks" "github.com/system-transparency/stfe/pkg/types" ) -func TestEndpointAddEntry(t *testing.T) { - for _, table := range []struct { - description string - breq *bytes.Buffer - trsp *trillian.QueueLeafResponse - terr error - wantCode int - }{ - { - description: "invalid: bad request: empty", - breq: bytes.NewBuffer(nil), - wantCode: http.StatusBadRequest, +var ( + testWitVK = [types.VerificationKeySize]byte{} + testConfig = Config{ + LogID: hex.EncodeToString(types.Hash([]byte("logid"))[:]), + TreeID: 0, + Prefix: "testonly", + MaxRange: 3, + Deadline: 10, + Interval: 10, + Witnesses: map[[types.HashSize]byte][types.VerificationKeySize]byte{ + *types.Hash(testWitVK[:]): testWitVK, }, - { - description: "invalid: bad Trillian response: error", - breq: testdata.AddSignedChecksumBuffer(t, testdata.Ed25519SkSubmitter, testdata.Ed25519VkSubmitter), - terr: fmt.Errorf("backend failure"), - wantCode: http.StatusInternalServerError, + } + testSTH = &types.SignedTreeHead{ + TreeHead: types.TreeHead{ + Timestamp: 0, + TreeSize: 0, + RootHash: types.Hash(nil), }, - { - description: "valid", - breq: testdata.AddSignedChecksumBuffer(t, testdata.Ed25519SkSubmitter, testdata.Ed25519VkSubmitter), - trsp: testdata.DefaultTQlr(t, false), - wantCode: http.StatusOK, + SigIdent: []*types.SigIdent{ + &types.SigIdent{ + Signature: &[types.SignatureSize]byte{}, + KeyHash: &[types.HashSize]byte{}, + }, }, - } { - func() { // run deferred functions at the end of each iteration - ti := newTestInstance(t, nil) - defer ti.ctrl.Finish() - - url := EndpointAddEntry.Path("http://example.com", ti.instance.LogParameters.Prefix) - req, err := http.NewRequest("POST", url, table.breq) - if err != nil { - t.Fatalf("must create http request: %v", err) - } - req.Header.Set("Content-Type", "application/octet-stream") - if table.trsp != nil || table.terr != nil { - ti.client.EXPECT().QueueLeaf(newDeadlineMatcher(), gomock.Any()).Return(table.trsp, table.terr) - } + } +) - w := httptest.NewRecorder() - ti.postHandler(t, EndpointAddEntry).ServeHTTP(w, req) - if got, want := w.Code, table.wantCode; got != want { - t.Errorf("got error code %d but wanted %d in test %q", got, want, table.description) - } - }() +func mustHandle(t *testing.T, i Instance, e types.Endpoint) Handler { + for _, handler := range i.Handlers() { + if handler.Endpoint == e { + return handler + } } + t.Fatalf("must handle endpoint: %v", e) + return Handler{} } -func TestEndpointAddCosignature(t *testing.T) { +func TestAddLeaf(t *testing.T) { + buf := func() io.Reader { + // A valid leaf request that was created manually + return bytes.NewBufferString(fmt.Sprintf( + "%s%s%s%s"+"%s%s%s%s"+"%s%s%s%s"+"%s%s%s%s"+"%s%s%s%s", + types.ShardHint, types.Delim, "0", types.EOL, + types.Checksum, types.Delim, "0000000000000000000000000000000000000000000000000000000000000000", types.EOL, + types.SignatureOverMessage, types.Delim, "4cb410a4d48f52f761a7c01abcc28fd71811b84ded5403caed5e21b374f6aac9637cecd36828f17529fd503413d30ab66d7bb37a31dbf09a90d23b9241c45009", types.EOL, + types.VerificationKey, types.Delim, "f2b7a00b625469d32502e06e8b7fad1ef258d4ad0c6cd87b846142ab681957d5", types.EOL, + types.DomainHint, types.Delim, "example.com", types.EOL, + )) + } for _, table := range []struct { description string - breq *bytes.Buffer - wantCode int + ascii io.Reader // buffer used to populate HTTP request + expect bool // set if a mock answer is expected + err error // error from Trillian client + wantCode int // HTTP status ok }{ { - description: "invalid: bad request: empty", - breq: bytes.NewBuffer(nil), + description: "invalid: bad request (parser error)", + ascii: bytes.NewBufferString("key=value\n"), wantCode: http.StatusBadRequest, }, { - description: "invalid: signed wrong sth", // newLogParameters() use testdata.Ed25519VkLog as default - breq: testdata.AddCosignatureBuffer(t, testdata.DefaultSth(t, testdata.Ed25519VkLog2), &testdata.Ed25519SkWitness, &testdata.Ed25519VkWitness), - wantCode: http.StatusBadRequest, + description: "invalid: bad request (signature error)", + ascii: bytes.NewBufferString(fmt.Sprintf( + "%s%s%s%s"+"%s%s%s%s"+"%s%s%s%s"+"%s%s%s%s"+"%s%s%s%s", + types.ShardHint, types.Delim, "1", types.EOL, + types.Checksum, types.Delim, "1111111111111111111111111111111111111111111111111111111111111111", types.EOL, + types.SignatureOverMessage, types.Delim, "4cb410a4d48f52f761a7c01abcc28fd71811b84ded5403caed5e21b374f6aac9637cecd36828f17529fd503413d30ab66d7bb37a31dbf09a90d23b9241c45009", types.EOL, + types.VerificationKey, types.Delim, "f2b7a00b625469d32502e06e8b7fad1ef258d4ad0c6cd87b846142ab681957d5", types.EOL, + types.DomainHint, types.Delim, "example.com", types.EOL, + )), + wantCode: http.StatusBadRequest, + }, + { + description: "invalid: backend failure", + ascii: buf(), + expect: true, + err: fmt.Errorf("something went wrong"), + wantCode: http.StatusInternalServerError, }, { description: "valid", - breq: testdata.AddCosignatureBuffer(t, testdata.DefaultSth(t, testdata.Ed25519VkLog), &testdata.Ed25519SkWitness, &testdata.Ed25519VkWitness), + ascii: buf(), + expect: true, wantCode: http.StatusOK, }, } { - func() { // run deferred functions at the end of each iteration - ti := newTestInstance(t, nil) - defer ti.ctrl.Finish() - - url := EndpointAddCosignature.Path("http://example.com", ti.instance.LogParameters.Prefix) - req, err := http.NewRequest("POST", url, table.breq) + // Run deferred functions at the end of each iteration + func() { + ctrl := gomock.NewController(t) + defer ctrl.Finish() + client := mocks.NewMockClient(ctrl) + if table.expect { + client.EXPECT().AddLeaf(gomock.Any(), gomock.Any()).Return(table.err) + } + i := Instance{ + Config: testConfig, + Client: client, + } + + // Create HTTP request + url := types.EndpointAddLeaf.Path("http://example.com", i.Prefix) + req, err := http.NewRequest("POST", url, table.ascii) if err != nil { t.Fatalf("must create http request: %v", err) } - req.Header.Set("Content-Type", "application/octet-stream") + // Run HTTP request w := httptest.NewRecorder() - ti.postHandler(t, EndpointAddCosignature).ServeHTTP(w, req) + mustHandle(t, i, types.EndpointAddLeaf).ServeHTTP(w, req) if got, want := w.Code, table.wantCode; got != want { - t.Errorf("got error code %d but wanted %d in test %q", got, want, table.description) + t.Errorf("got HTTP status code %v but wanted %v in test %q", got, want, table.description) } }() } } -func TestEndpointGetLatestSth(t *testing.T) { +func TestAddCosignature(t *testing.T) { + buf := func() io.Reader { + return bytes.NewBufferString(fmt.Sprintf( + "%s%s%x%s"+"%s%s%x%s", + types.Signature, types.Delim, make([]byte, types.SignatureSize), types.EOL, + types.KeyHash, types.Delim, *types.Hash(testWitVK[:]), types.EOL, + )) + } for _, table := range []struct { description string - trsp *trillian.GetLatestSignedLogRootResponse - terr error - wantCode int - wantItem *types.StItem + ascii io.Reader // buffer used to populate HTTP request + expect bool // set if a mock answer is expected + err error // error from Trillian client + wantCode int // HTTP status ok }{ { - description: "backend failure", - terr: fmt.Errorf("backend failure"), - wantCode: http.StatusInternalServerError, + description: "invalid: bad request (parser error)", + ascii: bytes.NewBufferString("key=value\n"), + wantCode: http.StatusBadRequest, }, { - description: "valid", - trsp: testdata.DefaultTSlr(t), - wantCode: http.StatusOK, - wantItem: testdata.DefaultSth(t, testdata.Ed25519VkLog), + description: "invalid: bad request (unknown witness)", + ascii: bytes.NewBufferString(fmt.Sprintf( + "%s%s%x%s"+"%s%s%x%s", + types.Signature, types.Delim, make([]byte, types.SignatureSize), types.EOL, + types.KeyHash, types.Delim, *types.Hash(testWitVK[1:]), types.EOL, + )), + wantCode: http.StatusBadRequest, }, - } { - func() { // run deferred functions at the end of each iteration - ti := newTestInstance(t, cttestdata.NewSignerWithFixedSig(nil, testdata.Signature)) - ti.ctrl.Finish() - - // Setup and run client query - url := EndpointGetLatestSth.Path("http://example.com", ti.instance.LogParameters.Prefix) - req, err := http.NewRequest("GET", url, nil) - if err != nil { - t.Fatalf("must create http request: %v", err) - } - if table.trsp != nil || table.terr != nil { - ti.client.EXPECT().GetLatestSignedLogRoot(newDeadlineMatcher(), gomock.Any()).Return(table.trsp, table.terr) - } - - w := httptest.NewRecorder() - ti.getHandler(t, EndpointGetLatestSth).ServeHTTP(w, req) - if got, want := w.Code, table.wantCode; got != want { - t.Errorf("got error code %d but wanted %d in test %q", got, want, table.description) - } - if w.Code != http.StatusOK { - return - } - - var item types.StItem - if err := types.Unmarshal([]byte(w.Body.String()), &item); err != nil { - t.Errorf("valid response cannot be unmarshalled in test %q: %v", table.description, err) - } - if got, want := item, *table.wantItem; !reflect.DeepEqual(got, want) { - t.Errorf("got item\n%v\n\tbut wanted\n%v\n\tin test %q", got, want, table.description) - } - }() - } -} - -func TestEndpointGetStableSth(t *testing.T) { - for _, table := range []struct { - description string - useBadSource bool - wantCode int - wantItem *types.StItem - }{ { - description: "invalid: sth source failure", - useBadSource: true, - wantCode: http.StatusInternalServerError, + description: "invalid: backend failure", + ascii: buf(), + expect: true, + err: fmt.Errorf("something went wrong"), + wantCode: http.StatusBadRequest, }, { description: "valid", + ascii: buf(), + expect: true, wantCode: http.StatusOK, - wantItem: testdata.DefaultSth(t, testdata.Ed25519VkLog), }, } { - func() { // run deferred functions at the end of each iteration - ti := newTestInstance(t, nil) - ti.ctrl.Finish() - if table.useBadSource { - ti.instance.SthSource = &ActiveSthSource{} - } - - // Setup and run client query - url := EndpointGetStableSth.Path("http://example.com", ti.instance.LogParameters.Prefix) - req, err := http.NewRequest("GET", url, nil) + // Run deferred functions at the end of each iteration + func() { + ctrl := gomock.NewController(t) + defer ctrl.Finish() + stateman := mocks.NewMockStateManager(ctrl) + if table.expect { + stateman.EXPECT().AddCosignature(gomock.Any(), gomock.Any(), gomock.Any()).Return(table.err) + } + i := Instance{ + Config: testConfig, + Stateman: stateman, + } + + // Create HTTP request + url := types.EndpointAddCosignature.Path("http://example.com", i.Prefix) + req, err := http.NewRequest("POST", url, table.ascii) if err != nil { t.Fatalf("must create http request: %v", err) } + // Run HTTP request w := httptest.NewRecorder() - ti.getHandler(t, EndpointGetStableSth).ServeHTTP(w, req) + mustHandle(t, i, types.EndpointAddCosignature).ServeHTTP(w, req) if got, want := w.Code, table.wantCode; got != want { - t.Errorf("got error code %d but wanted %d in test %q", got, want, table.description) - } - if w.Code != http.StatusOK { - return - } - - var item types.StItem - if err := types.Unmarshal([]byte(w.Body.String()), &item); err != nil { - t.Errorf("valid response cannot be unmarshalled in test %q: %v", table.description, err) - } - if got, want := item, *table.wantItem; !reflect.DeepEqual(got, want) { - t.Errorf("got item\n%v\n\tbut wanted\n%v\n\tin test %q", got, want, table.description) + t.Errorf("got HTTP status code %v but wanted %v in test %q", got, want, table.description) } }() } } -func TestEndpointGetCosignedSth(t *testing.T) { +func TestGetTreeHeadLatest(t *testing.T) { for _, table := range []struct { - description string - useBadSource bool - wantCode int - wantItem *types.StItem + description string + expect bool // set if a mock answer is expected + rsp *types.SignedTreeHead // signed tree head from Trillian client + err error // error from Trillian client + wantCode int // HTTP status ok }{ { - description: "invalid: sth source failure", - useBadSource: true, - wantCode: http.StatusInternalServerError, + description: "invalid: backend failure", + expect: true, + err: fmt.Errorf("something went wrong"), + wantCode: http.StatusInternalServerError, }, { description: "valid", + expect: true, + rsp: testSTH, wantCode: http.StatusOK, - wantItem: testdata.DefaultCosth(t, testdata.Ed25519VkLog, [][32]byte{testdata.Ed25519VkWitness}), }, } { - func() { // run deferred functions at the end of each iteration - ti := newTestInstance(t, nil) - ti.ctrl.Finish() - if table.useBadSource { - ti.instance.SthSource = &ActiveSthSource{} + // Run deferred functions at the end of each iteration + func() { + ctrl := gomock.NewController(t) + defer ctrl.Finish() + stateman := mocks.NewMockStateManager(ctrl) + if table.expect { + stateman.EXPECT().Latest(gomock.Any()).Return(table.rsp, table.err) + } + i := Instance{ + Config: testConfig, + Stateman: stateman, } - // Setup and run client query - url := EndpointGetCosignedSth.Path("http://example.com", ti.instance.LogParameters.Prefix) + // Create HTTP request + url := types.EndpointGetTreeHeadLatest.Path("http://example.com", i.Prefix) req, err := http.NewRequest("GET", url, nil) if err != nil { t.Fatalf("must create http request: %v", err) } + // Run HTTP request w := httptest.NewRecorder() - ti.getHandler(t, EndpointGetCosignedSth).ServeHTTP(w, req) + mustHandle(t, i, types.EndpointGetTreeHeadLatest).ServeHTTP(w, req) if got, want := w.Code, table.wantCode; got != want { - t.Errorf("got error code %d but wanted %d in test %q", got, want, table.description) - } - if w.Code != http.StatusOK { - return - } - - var item types.StItem - if err := types.Unmarshal([]byte(w.Body.String()), &item); err != nil { - t.Errorf("valid response cannot be unmarshalled in test %q: %v", table.description, err) - } - if got, want := item, *table.wantItem; !reflect.DeepEqual(got, want) { - t.Errorf("got item\n%v\n\tbut wanted\n%v\n\tin test %q", got, want, table.description) + t.Errorf("got HTTP status code %v but wanted %v in test %q", got, want, table.description) } }() } } -func TestEndpointGetProofByHash(t *testing.T) { +func TestGetTreeToSign(t *testing.T) { for _, table := range []struct { description string - breq *bytes.Buffer - trsp *trillian.GetInclusionProofByHashResponse - terr error - wantCode int - wantItem *types.StItem + expect bool // set if a mock answer is expected + rsp *types.SignedTreeHead // signed tree head from Trillian client + err error // error from Trillian client + wantCode int // HTTP status ok }{ { - description: "invalid: bad request: empty", - breq: bytes.NewBuffer(nil), - wantCode: http.StatusBadRequest, - }, - { - description: "invalid: bad Trillian response: error", - breq: bytes.NewBuffer(marshal(t, types.GetProofByHashV1{TreeSize: 1, Hash: testdata.LeafHash})), - terr: fmt.Errorf("backend failure"), + description: "invalid: backend failure", + expect: true, + err: fmt.Errorf("something went wrong"), wantCode: http.StatusInternalServerError, }, { description: "valid", - breq: bytes.NewBuffer(marshal(t, types.GetProofByHashV1{TreeSize: 1, Hash: testdata.LeafHash})), - trsp: testdata.DefaultTGipbhr(t), + expect: true, + rsp: testSTH, wantCode: http.StatusOK, - wantItem: testdata.DefaultInclusionProof(t, 1), }, } { - func() { // run deferred functions at the end of each iteration - ti := newTestInstance(t, nil) - defer ti.ctrl.Finish() + // Run deferred functions at the end of each iteration + func() { + ctrl := gomock.NewController(t) + defer ctrl.Finish() + stateman := mocks.NewMockStateManager(ctrl) + if table.expect { + stateman.EXPECT().ToSign(gomock.Any()).Return(table.rsp, table.err) + } + i := Instance{ + Config: testConfig, + Stateman: stateman, + } - url := EndpointGetProofByHash.Path("http://example.com", ti.instance.LogParameters.Prefix) - req, err := http.NewRequest("POST", url, table.breq) + // Create HTTP request + url := types.EndpointGetTreeHeadToSign.Path("http://example.com", i.Prefix) + req, err := http.NewRequest("GET", url, nil) if err != nil { t.Fatalf("must create http request: %v", err) } - req.Header.Set("Content-Type", "application/octet-stream") - if table.trsp != nil || table.terr != nil { - ti.client.EXPECT().GetInclusionProofByHash(newDeadlineMatcher(), gomock.Any()).Return(table.trsp, table.terr) - } + // Run HTTP request w := httptest.NewRecorder() - ti.postHandler(t, EndpointGetProofByHash).ServeHTTP(w, req) + mustHandle(t, i, types.EndpointGetTreeHeadToSign).ServeHTTP(w, req) if got, want := w.Code, table.wantCode; got != want { - t.Errorf("got error code %d but wanted %d in test %q", got, want, table.description) - } - if w.Code != http.StatusOK { - return - } - - var item types.StItem - if err := types.Unmarshal([]byte(w.Body.String()), &item); err != nil { - t.Errorf("valid response cannot be unmarshalled in test %q: %v", table.description, err) - } - if got, want := item, *table.wantItem; !reflect.DeepEqual(got, want) { - t.Errorf("got item\n%v\n\tbut wanted\n%v\n\tin test %q", got, want, table.description) + t.Errorf("got HTTP status code %v but wanted %v in test %q", got, want, table.description) } }() } } -func TestEndpointGetConsistencyProof(t *testing.T) { +func TestGetTreeCosigned(t *testing.T) { for _, table := range []struct { description string - breq *bytes.Buffer - trsp *trillian.GetConsistencyProofResponse - terr error - wantCode int - wantItem *types.StItem + expect bool // set if a mock answer is expected + rsp *types.SignedTreeHead // signed tree head from Trillian client + err error // error from Trillian client + wantCode int // HTTP status ok }{ { - description: "invalid: bad request: empty", - breq: bytes.NewBuffer(nil), - wantCode: http.StatusBadRequest, - }, - { - description: "invalid: bad Trillian response: error", - breq: bytes.NewBuffer(marshal(t, types.GetConsistencyProofV1{First: 1, Second: 2})), - terr: fmt.Errorf("backend failure"), + description: "invalid: backend failure", + expect: true, + err: fmt.Errorf("something went wrong"), wantCode: http.StatusInternalServerError, }, { description: "valid", - breq: bytes.NewBuffer(marshal(t, types.GetConsistencyProofV1{First: 1, Second: 2})), - trsp: testdata.DefaultTGcpr(t), + expect: true, + rsp: testSTH, wantCode: http.StatusOK, - wantItem: testdata.DefaultConsistencyProof(t, 1, 2), }, } { - func() { // run deferred functions at the end of each iteration - ti := newTestInstance(t, nil) - defer ti.ctrl.Finish() + // Run deferred functions at the end of each iteration + func() { + ctrl := gomock.NewController(t) + defer ctrl.Finish() + stateman := mocks.NewMockStateManager(ctrl) + if table.expect { + stateman.EXPECT().Cosigned(gomock.Any()).Return(table.rsp, table.err) + } + i := Instance{ + Config: testConfig, + Stateman: stateman, + } - url := EndpointGetConsistencyProof.Path("http://example.com", ti.instance.LogParameters.Prefix) - req, err := http.NewRequest("POST", url, table.breq) + // Create HTTP request + url := types.EndpointGetTreeHeadCosigned.Path("http://example.com", i.Prefix) + req, err := http.NewRequest("GET", url, nil) if err != nil { t.Fatalf("must create http request: %v", err) } - req.Header.Set("Content-Type", "application/octet-stream") - if table.trsp != nil || table.terr != nil { - ti.client.EXPECT().GetConsistencyProof(newDeadlineMatcher(), gomock.Any()).Return(table.trsp, table.terr) - } + // Run HTTP request w := httptest.NewRecorder() - ti.postHandler(t, EndpointGetConsistencyProof).ServeHTTP(w, req) + mustHandle(t, i, types.EndpointGetTreeHeadCosigned).ServeHTTP(w, req) if got, want := w.Code, table.wantCode; got != want { - t.Errorf("got error code %d but wanted %d in test %q", got, want, table.description) - } - if w.Code != http.StatusOK { - return - } - - var item types.StItem - if err := types.Unmarshal([]byte(w.Body.String()), &item); err != nil { - t.Errorf("valid response cannot be unmarshalled in test %q: %v", table.description, err) - } - if got, want := item, *table.wantItem; !reflect.DeepEqual(got, want) { - t.Errorf("got item\n%v\n\tbut wanted\n%v\n\tin test %q", got, want, table.description) + t.Errorf("got HTTP status code %v but wanted %v in test %q", got, want, table.description) } }() } } -func TestEndpointGetEntriesV1(t *testing.T) { +func TestGetConsistencyProof(t *testing.T) { + buf := func(oldSize, newSize int) io.Reader { + return bytes.NewBufferString(fmt.Sprintf( + "%s%s%d%s"+"%s%s%d%s", + types.OldSize, types.Delim, oldSize, types.EOL, + types.NewSize, types.Delim, newSize, types.EOL, + )) + } + // values in testProof are not relevant for the test, just need a path + testProof := &types.ConsistencyProof{ + OldSize: 1, + NewSize: 2, + Path: []*[types.HashSize]byte{ + types.Hash(nil), + }, + } for _, table := range []struct { description string - breq *bytes.Buffer - trsp *trillian.GetLeavesByRangeResponse - terr error - wantCode int - wantItem *types.StItemList + ascii io.Reader // buffer used to populate HTTP request + expect bool // set if a mock answer is expected + rsp *types.ConsistencyProof // consistency proof from Trillian client + err error // error from Trillian client + wantCode int // HTTP status ok }{ { - description: "invalid: bad request: empty", - breq: bytes.NewBuffer(nil), + description: "invalid: bad request (parser error)", + ascii: bytes.NewBufferString("key=value\n"), wantCode: http.StatusBadRequest, }, { - description: "invalid: bad Trillian response: error", - breq: bytes.NewBuffer(marshal(t, types.GetEntriesV1{Start: 0, End: 0})), - terr: fmt.Errorf("backend failure"), - wantCode: http.StatusInternalServerError, - }, - { - description: "valid", // remember that newLogParameters() have testdata.MaxRange configured - breq: bytes.NewBuffer(marshal(t, types.GetEntriesV1{Start: 0, End: uint64(testdata.MaxRange - 1)})), - trsp: testdata.DefaultTGlbrr(t, 0, testdata.MaxRange-1), + description: "valid", + ascii: buf(1, 2), + expect: true, + rsp: testProof, wantCode: http.StatusOK, - wantItem: testdata.DefaultStItemList(t, 0, uint64(testdata.MaxRange)-1), }, } { - func() { // run deferred functions at the end of each iteration - ti := newTestInstance(t, nil) - defer ti.ctrl.Finish() - - url := EndpointGetEntries.Path("http://example.com", ti.instance.LogParameters.Prefix) - req, err := http.NewRequest("POST", url, table.breq) + // Run deferred functions at the end of each iteration + func() { + ctrl := gomock.NewController(t) + defer ctrl.Finish() + client := mocks.NewMockClient(ctrl) + if table.expect { + client.EXPECT().GetConsistencyProof(gomock.Any(), gomock.Any()).Return(table.rsp, table.err) + } + i := Instance{ + Config: testConfig, + Client: client, + } + + // Create HTTP request + url := types.EndpointGetConsistencyProof.Path("http://example.com", i.Prefix) + req, err := http.NewRequest("POST", url, table.ascii) if err != nil { t.Fatalf("must create http request: %v", err) } - req.Header.Set("Content-Type", "application/octet-stream") - if table.trsp != nil || table.terr != nil { - ti.client.EXPECT().GetLeavesByRange(newDeadlineMatcher(), gomock.Any()).Return(table.trsp, table.terr) - } + // Run HTTP request w := httptest.NewRecorder() - ti.postHandler(t, EndpointGetEntries).ServeHTTP(w, req) + mustHandle(t, i, types.EndpointGetConsistencyProof).ServeHTTP(w, req) if got, want := w.Code, table.wantCode; got != want { - t.Errorf("got error code %d but wanted %d in test %q", got, want, table.description) - } - if w.Code != http.StatusOK { - return - } - - var item types.StItemList - if err := types.Unmarshal([]byte(w.Body.String()), &item); err != nil { - t.Errorf("valid response cannot be unmarshalled in test %q: %v", table.description, err) - } - if got, want := item, *table.wantItem; !reflect.DeepEqual(got, want) { - t.Errorf("got item\n%v\n\tbut wanted\n%v\n\tin test %q", got, want, table.description) + t.Errorf("got HTTP status code %v but wanted %v in test %q", got, want, table.description) } }() } } -// TODO: TestWriteOctetResponse -func TestWriteOctetResponse(t *testing.T) { -} - -// deadlineMatcher implements gomock.Matcher, such that an error is raised if -// there is no context.Context deadline set -type deadlineMatcher struct{} - -// newDeadlineMatcher returns a new DeadlineMatcher -func newDeadlineMatcher() gomock.Matcher { - return &deadlineMatcher{} -} - -// Matches returns true if the passed interface is a context with a deadline -func (dm *deadlineMatcher) Matches(i interface{}) bool { - ctx, ok := i.(context.Context) - if !ok { - return false - } - _, ok = ctx.Deadline() - return ok +func TestGetInclusionProof(t *testing.T) { } -// String is needed to implement gomock.Matcher -func (dm *deadlineMatcher) String() string { - return fmt.Sprintf("deadlineMatcher{}") +func TestGetLeaves(t *testing.T) { } diff --git a/pkg/instance/instance.go b/pkg/instance/instance.go index 3441a0a..c2fe8fa 100644 --- a/pkg/instance/instance.go +++ b/pkg/instance/instance.go @@ -3,6 +3,7 @@ package stfe import ( "context" "crypto" + "crypto/ed25519" "fmt" "net/http" "time" @@ -88,3 +89,71 @@ func (a Handler) ServeHTTP(w http.ResponseWriter, r *http.Request) { http.Error(w, fmt.Sprintf("%s%s%s%s", "Error", types.Delim, err.Error(), types.EOL), statusCode) } } + +func (i *Instance) leafRequestFromHTTP(r *http.Request) (*types.LeafRequest, error) { + var req types.LeafRequest + if err := req.UnmarshalASCII(r.Body); err != nil { + return nil, fmt.Errorf("UnmarshalASCII: %v", err) + } + + vk := ed25519.PublicKey(req.VerificationKey[:]) + msg := req.Message.Marshal() + sig := req.Signature[:] + if !ed25519.Verify(vk, msg, sig) { + return nil, fmt.Errorf("invalid signature") + } + // TODO: check shard hint + // TODO: check domain hint + return &req, nil +} + +func (i *Instance) cosignatureRequestFromHTTP(r *http.Request) (*types.CosignatureRequest, error) { + var req types.CosignatureRequest + if err := req.UnmarshalASCII(r.Body); err != nil { + return nil, fmt.Errorf("unpackOctetPost: %v", err) + } + if _, ok := i.Witnesses[*req.KeyHash]; !ok { + return nil, fmt.Errorf("Unknown witness: %x", req.KeyHash) + } + return &req, nil +} + +func (i *Instance) consistencyProofRequestFromHTTP(r *http.Request) (*types.ConsistencyProofRequest, error) { + var req types.ConsistencyProofRequest + if err := req.UnmarshalASCII(r.Body); err != nil { + return nil, fmt.Errorf("UnmarshalASCII: %v", err) + } + if req.OldSize < 1 { + return nil, fmt.Errorf("OldSize(%d) must be larger than zero", req.OldSize) + } + if req.NewSize <= req.OldSize { + return nil, fmt.Errorf("NewSize(%d) must be larger than OldSize(%d)", req.NewSize, req.OldSize) + } + return &req, nil +} + +func (i *Instance) inclusionProofRequestFromHTTP(r *http.Request) (*types.InclusionProofRequest, error) { + var req types.InclusionProofRequest + if err := req.UnmarshalASCII(r.Body); err != nil { + return nil, fmt.Errorf("UnmarshalASCII: %v", err) + } + if req.TreeSize < 1 { + return nil, fmt.Errorf("TreeSize(%d) must be larger than zero", req.TreeSize) + } + return &req, nil +} + +func (i *Instance) leavesRequestFromHTTP(r *http.Request) (*types.LeavesRequest, error) { + var req types.LeavesRequest + if err := req.UnmarshalASCII(r.Body); err != nil { + return nil, fmt.Errorf("UnmarshalASCII: %v", err) + } + + if req.StartSize > req.EndSize { + return nil, fmt.Errorf("StartSize(%d) must be less than or equal to EndSize(%d)", req.StartSize, req.EndSize) + } + if req.EndSize-req.StartSize+1 > uint64(i.MaxRange) { + req.EndSize = req.StartSize + uint64(i.MaxRange) - 1 + } + return &req, nil +} diff --git a/pkg/instance/instance_test.go b/pkg/instance/instance_test.go index a7a3d8a..45a2837 100644 --- a/pkg/instance/instance_test.go +++ b/pkg/instance/instance_test.go @@ -1,158 +1,9 @@ package stfe import ( - "crypto" - "net/http" - "net/http/httptest" "testing" - - "github.com/golang/mock/gomock" - "github.com/google/certificate-transparency-go/trillian/mockclient" - "github.com/system-transparency/stfe/pkg/testdata" - "github.com/system-transparency/stfe/pkg/types" ) -type testInstance struct { - ctrl *gomock.Controller - client *mockclient.MockTrillianLogClient - instance *Instance -} - -// newTestInstances sets up a test instance that uses default log parameters -// with an optional signer, see newLogParameters() for further details. The -// SthSource is instantiated with an ActiveSthSource that has (i) the default -// STH as the currently cosigned STH based on testdata.Ed25519VkWitness, and -// (ii) the default STH without any cosignatures as the currently stable STH. -func newTestInstance(t *testing.T, signer crypto.Signer) *testInstance { - t.Helper() - ctrl := gomock.NewController(t) - client := mockclient.NewMockTrillianLogClient(ctrl) - return &testInstance{ - ctrl: ctrl, - client: client, - instance: &Instance{ - Client: client, - LogParameters: newLogParameters(t, signer), - SthSource: &ActiveSthSource{ - client: client, - logParameters: newLogParameters(t, signer), - currCosth: testdata.DefaultCosth(t, testdata.Ed25519VkLog, [][32]byte{testdata.Ed25519VkWitness}), - nextCosth: testdata.DefaultCosth(t, testdata.Ed25519VkLog, nil), - cosignatureFrom: make(map[[types.NamespaceFingerprintSize]byte]bool), - }, - }, - } -} - -// getHandlers returns all endpoints that use HTTP GET as a map to handlers -func (ti *testInstance) getHandlers(t *testing.T) map[Endpoint]Handler { - t.Helper() - return map[Endpoint]Handler{ - EndpointGetLatestSth: Handler{Instance: ti.instance, Handler: getLatestSth, Endpoint: EndpointGetLatestSth, Method: http.MethodGet}, - EndpointGetStableSth: Handler{Instance: ti.instance, Handler: getStableSth, Endpoint: EndpointGetStableSth, Method: http.MethodGet}, - EndpointGetCosignedSth: Handler{Instance: ti.instance, Handler: getCosignedSth, Endpoint: EndpointGetCosignedSth, Method: http.MethodGet}, - } -} - -// postHandlers returns all endpoints that use HTTP POST as a map to handlers -func (ti *testInstance) postHandlers(t *testing.T) map[Endpoint]Handler { - t.Helper() - return map[Endpoint]Handler{ - EndpointAddEntry: Handler{Instance: ti.instance, Handler: addEntry, Endpoint: EndpointAddEntry, Method: http.MethodPost}, - EndpointAddCosignature: Handler{Instance: ti.instance, Handler: addCosignature, Endpoint: EndpointAddCosignature, Method: http.MethodPost}, - EndpointGetConsistencyProof: Handler{Instance: ti.instance, Handler: getConsistencyProof, Endpoint: EndpointGetConsistencyProof, Method: http.MethodPost}, - EndpointGetProofByHash: Handler{Instance: ti.instance, Handler: getProofByHash, Endpoint: EndpointGetProofByHash, Method: http.MethodPost}, - EndpointGetEntries: Handler{Instance: ti.instance, Handler: getEntries, Endpoint: EndpointGetEntries, Method: http.MethodPost}, - } -} - -// getHandler must return a particular HTTP GET handler -func (ti *testInstance) getHandler(t *testing.T, endpoint Endpoint) Handler { - t.Helper() - handler, ok := ti.getHandlers(t)[endpoint] - if !ok { - t.Fatalf("must return HTTP GET handler for endpoint: %s", endpoint) - } - return handler -} - -// postHandler must return a particular HTTP POST handler -func (ti *testInstance) postHandler(t *testing.T, endpoint Endpoint) Handler { - t.Helper() - handler, ok := ti.postHandlers(t)[endpoint] - if !ok { - t.Fatalf("must return HTTP POST handler for endpoint: %s", endpoint) - } - return handler -} - -// TestHandlers checks that we configured all endpoints and that there are no -// unexpected ones. -func TestHandlers(t *testing.T) { - endpoints := map[Endpoint]bool{ - EndpointAddEntry: false, - EndpointAddCosignature: false, - EndpointGetLatestSth: false, - EndpointGetStableSth: false, - EndpointGetCosignedSth: false, - EndpointGetConsistencyProof: false, - EndpointGetProofByHash: false, - EndpointGetEntries: false, - } - i := &Instance{nil, newLogParameters(t, nil), nil} - for _, handler := range i.Handlers() { - if _, ok := endpoints[handler.Endpoint]; !ok { - t.Errorf("got unexpected endpoint: %s", handler.Endpoint) - } - endpoints[handler.Endpoint] = true - } - for endpoint, ok := range endpoints { - if !ok { - t.Errorf("endpoint %s is not configured", endpoint) - } - } -} - -// TestGetHandlersRejectPost checks that all get handlers reject post requests -func TestGetHandlersRejectPost(t *testing.T) { - ti := newTestInstance(t, nil) - defer ti.ctrl.Finish() - - for endpoint, handler := range ti.getHandlers(t) { - t.Run(string(endpoint), func(t *testing.T) { - s := httptest.NewServer(handler) - defer s.Close() - - url := endpoint.Path(s.URL, ti.instance.LogParameters.Prefix) - if rsp, err := http.Post(url, "application/json", nil); err != nil { - t.Fatalf("http.Post(%s)=(_,%q), want (_,nil)", url, err) - } else if rsp.StatusCode != http.StatusMethodNotAllowed { - t.Errorf("http.Post(%s)=(%d,nil), want (%d, nil)", url, rsp.StatusCode, http.StatusMethodNotAllowed) - } - }) - } -} - -// TestPostHandlersRejectGet checks that all post handlers reject get requests -func TestPostHandlersRejectGet(t *testing.T) { - ti := newTestInstance(t, nil) - defer ti.ctrl.Finish() - - for endpoint, handler := range ti.postHandlers(t) { - t.Run(string(endpoint), func(t *testing.T) { - s := httptest.NewServer(handler) - defer s.Close() - - url := endpoint.Path(s.URL, ti.instance.LogParameters.Prefix) - if rsp, err := http.Get(url); err != nil { - t.Fatalf("http.Get(%s)=(_,%q), want (_,nil)", url, err) - } else if rsp.StatusCode != http.StatusMethodNotAllowed { - t.Errorf("http.Get(%s)=(%d,nil), want (%d, nil)", url, rsp.StatusCode, http.StatusMethodNotAllowed) - } - }) - } -} - -// TODO: TestHandlerPath -func TestHandlerPath(t *testing.T) { -} +func TestHandlers(t *testing.T) {} +func TestPath(t *testing.T) {} +func TestServeHTTP(t *testing.T) {} diff --git a/pkg/instance/metric.go b/pkg/instance/metric.go index 7e3e8b2..db11bd2 100644 --- a/pkg/instance/metric.go +++ b/pkg/instance/metric.go @@ -6,11 +6,9 @@ import ( ) var ( - reqcnt monitoring.Counter // number of incoming http requests - rspcnt monitoring.Counter // number of valid http responses - latency monitoring.Histogram // request-response latency - lastSthTimestamp monitoring.Gauge // unix timestamp from the most recent sth - lastSthSize monitoring.Gauge // tree size of most recent sth + reqcnt monitoring.Counter // number of incoming http requests + rspcnt monitoring.Counter // number of valid http responses + latency monitoring.Histogram // request-response latency ) func init() { @@ -18,6 +16,4 @@ func init() { reqcnt = mf.NewCounter("http_req", "number of http requests", "logid", "endpoint") rspcnt = mf.NewCounter("http_rsp", "number of http requests", "logid", "endpoint", "status") latency = mf.NewHistogram("http_latency", "http request-response latency", "logid", "endpoint", "status") - lastSthTimestamp = mf.NewGauge("last_sth_timestamp", "unix timestamp while handling the most recent sth", "logid") - lastSthSize = mf.NewGauge("last_sth_size", "most recent sth tree size", "logid") } diff --git a/pkg/instance/request.go b/pkg/instance/request.go deleted file mode 100644 index 7475b26..0000000 --- a/pkg/instance/request.go +++ /dev/null @@ -1,77 +0,0 @@ -package stfe - -import ( - "crypto/ed25519" - "fmt" - "net/http" - - "github.com/system-transparency/stfe/pkg/types" -) - -func (i *Instance) leafRequestFromHTTP(r *http.Request) (*types.LeafRequest, error) { - var req types.LeafRequest - if err := req.UnmarshalASCII(r.Body); err != nil { - return nil, fmt.Errorf("UnmarshalASCII: %v", err) - } - - vk := ed25519.PublicKey(req.VerificationKey[:]) - msg := req.Message.Marshal() - sig := req.Signature[:] - if !ed25519.Verify(vk, msg, sig) { - return nil, fmt.Errorf("invalid signature") - } - // TODO: check shard hint - // TODO: check domain hint - return &req, nil -} - -func (i *Instance) cosignatureRequestFromHTTP(r *http.Request) (*types.CosignatureRequest, error) { - var req types.CosignatureRequest - if err := req.UnmarshalASCII(r.Body); err != nil { - return nil, fmt.Errorf("unpackOctetPost: %v", err) - } - if _, ok := i.Witnesses[*req.KeyHash]; !ok { - return nil, fmt.Errorf("Unknown witness: %x", req.KeyHash) - } - return &req, nil -} - -func (i *Instance) consistencyProofRequestFromHTTP(r *http.Request) (*types.ConsistencyProofRequest, error) { - var req types.ConsistencyProofRequest - if err := req.UnmarshalASCII(r.Body); err != nil { - return nil, fmt.Errorf("UnmarshalASCII: %v", err) - } - if req.OldSize < 1 { - return nil, fmt.Errorf("OldSize(%d) must be larger than zero", req.OldSize) - } - if req.NewSize <= req.OldSize { - return nil, fmt.Errorf("NewSize(%d) must be larger than OldSize(%d)", req.NewSize, req.OldSize) - } - return &req, nil -} - -func (i *Instance) inclusionProofRequestFromHTTP(r *http.Request) (*types.InclusionProofRequest, error) { - var req types.InclusionProofRequest - if err := req.UnmarshalASCII(r.Body); err != nil { - return nil, fmt.Errorf("UnmarshalASCII: %v", err) - } - if req.TreeSize < 1 { - return nil, fmt.Errorf("TreeSize(%d) must be larger than zero", req.TreeSize) - } - return &req, nil -} - -func (i *Instance) leavesRequestFromHTTP(r *http.Request) (*types.LeavesRequest, error) { - var req types.LeavesRequest - if err := req.UnmarshalASCII(r.Body); err != nil { - return nil, fmt.Errorf("UnmarshalASCII: %v", err) - } - - if req.StartSize > req.EndSize { - return nil, fmt.Errorf("StartSize(%d) must be less than or equal to EndSize(%d)", req.StartSize, req.EndSize) - } - if req.EndSize-req.StartSize+1 > uint64(i.MaxRange) { - req.EndSize = req.StartSize + uint64(i.MaxRange) - 1 - } - return &req, nil -} diff --git a/pkg/instance/request_test.go b/pkg/instance/request_test.go deleted file mode 100644 index 0a5a908..0000000 --- a/pkg/instance/request_test.go +++ /dev/null @@ -1,318 +0,0 @@ -package stfe - -import ( - "bytes" - //"fmt" - "reflect" - "testing" - //"testing/iotest" - - "net/http" - - "github.com/system-transparency/stfe/pkg/testdata" - "github.com/system-transparency/stfe/pkg/types" -) - -func TestParseAddEntryV1Request(t *testing.T) { - lp := newLogParameters(t, nil) - for _, table := range []struct { - description string - breq *bytes.Buffer - wantErr bool - }{ - { - description: "invalid: nothing to unpack", - breq: bytes.NewBuffer(nil), - wantErr: true, - }, - { - description: "invalid: not a signed checksum entry", - breq: testdata.AddCosignatureBuffer(t, testdata.DefaultSth(t, testdata.Ed25519VkLog), &testdata.Ed25519SkWitness, &testdata.Ed25519VkWitness), - wantErr: true, - }, - { - description: "invalid: untrusted submitter", // only testdata.Ed25519VkSubmitter is registered by default in newLogParameters() - - breq: testdata.AddSignedChecksumBuffer(t, testdata.Ed25519SkSubmitter2, testdata.Ed25519VkSubmitter2), - wantErr: true, - }, - { - description: "invalid: signature does not cover message", - - breq: testdata.AddSignedChecksumBuffer(t, testdata.Ed25519SkSubmitter2, testdata.Ed25519VkSubmitter), - wantErr: true, - }, - { - description: "valid", - breq: testdata.AddSignedChecksumBuffer(t, testdata.Ed25519SkSubmitter, testdata.Ed25519VkSubmitter), - }, // TODO: add test case that disables submitter policy (i.e., unregistered namespaces are accepted) - } { - url := EndpointAddEntry.Path("http://example.com", lp.Prefix) - req, err := http.NewRequest("POST", url, table.breq) - if err != nil { - t.Fatalf("failed creating http request: %v", err) - } - req.Header.Set("Content-Type", "application/octet-stream") - - _, err = lp.parseAddEntryV1Request(req) - if got, want := err != nil, table.wantErr; got != want { - t.Errorf("got errror %v but wanted %v in test %q: %v", got, want, table.description, err) - } - } -} - -func TestParseAddCosignatureV1Request(t *testing.T) { - lp := newLogParameters(t, nil) - for _, table := range []struct { - description string - breq *bytes.Buffer - wantErr bool - }{ - { - description: "invalid: nothing to unpack", - breq: bytes.NewBuffer(nil), - wantErr: true, - }, - { - description: "invalid: not a cosigned sth", - breq: testdata.AddSignedChecksumBuffer(t, testdata.Ed25519SkSubmitter, testdata.Ed25519VkSubmitter), - wantErr: true, - }, - { - description: "invalid: no cosignature", - breq: testdata.AddCosignatureBuffer(t, testdata.DefaultSth(t, testdata.Ed25519VkLog), &testdata.Ed25519SkWitness, nil), - wantErr: true, - }, - { - description: "invalid: untrusted witness", // only testdata.Ed25519VkWitness is registered by default in newLogParameters() - breq: testdata.AddCosignatureBuffer(t, testdata.DefaultSth(t, testdata.Ed25519VkLog), &testdata.Ed25519SkWitness2, &testdata.Ed25519VkWitness2), - wantErr: true, - }, - { - description: "invalid: signature does not cover message", - breq: testdata.AddCosignatureBuffer(t, testdata.DefaultSth(t, testdata.Ed25519VkLog), &testdata.Ed25519SkWitness2, &testdata.Ed25519VkWitness), - wantErr: true, - }, - { - description: "valid", - breq: testdata.AddCosignatureBuffer(t, testdata.DefaultSth(t, testdata.Ed25519VkLog), &testdata.Ed25519SkWitness, &testdata.Ed25519VkWitness), - }, // TODO: add test case that disables witness policy (i.e., unregistered namespaces are accepted) - } { - url := EndpointAddCosignature.Path("http://example.com", lp.Prefix) - req, err := http.NewRequest("POST", url, table.breq) - if err != nil { - t.Fatalf("failed creating http request: %v", err) - } - req.Header.Set("Content-Type", "application/octet-stream") - - _, err = lp.parseAddCosignatureV1Request(req) - if got, want := err != nil, table.wantErr; got != want { - t.Errorf("got errror %v but wanted %v in test %q: %v", got, want, table.description, err) - } - } -} - -func TestNewGetConsistencyProofRequest(t *testing.T) { - lp := newLogParameters(t, nil) - for _, table := range []struct { - description string - req *types.GetConsistencyProofV1 - wantErr bool - }{ - { - description: "invalid: nothing to unpack", - req: nil, - wantErr: true, - }, - { - description: "invalid: first must be larger than zero", - req: &types.GetConsistencyProofV1{First: 0, Second: 0}, - wantErr: true, - }, - { - description: "invalid: second must be larger than first", - req: &types.GetConsistencyProofV1{First: 2, Second: 1}, - wantErr: true, - }, - { - description: "valid", - req: &types.GetConsistencyProofV1{First: 1, Second: 2}, - }, - } { - var buf *bytes.Buffer - if table.req == nil { - buf = bytes.NewBuffer(nil) - } else { - buf = bytes.NewBuffer(marshal(t, *table.req)) - } - - url := EndpointGetConsistencyProof.Path("http://example.com", lp.Prefix) - req, err := http.NewRequest("POST", url, buf) - if err != nil { - t.Fatalf("failed creating http request: %v", err) - } - req.Header.Set("Content-Type", "application/octet-stream") - - _, err = lp.parseGetConsistencyProofV1Request(req) - if got, want := err != nil, table.wantErr; got != want { - t.Errorf("got errror %v but wanted %v in test %q: %v", got, want, table.description, err) - } - } -} - -func TestNewGetProofByHashRequest(t *testing.T) { - lp := newLogParameters(t, nil) - for _, table := range []struct { - description string - req *types.GetProofByHashV1 - wantErr bool - }{ - { - description: "invalid: nothing to unpack", - req: nil, - wantErr: true, - }, - { - description: "invalid: no entry in an empty tree", - req: &types.GetProofByHashV1{TreeSize: 0, Hash: testdata.LeafHash}, - wantErr: true, - }, - { - description: "valid", - req: &types.GetProofByHashV1{TreeSize: 1, Hash: testdata.LeafHash}, - }, - } { - var buf *bytes.Buffer - if table.req == nil { - buf = bytes.NewBuffer(nil) - } else { - buf = bytes.NewBuffer(marshal(t, *table.req)) - } - - url := EndpointGetProofByHash.Path("http://example.com", lp.Prefix) - req, err := http.NewRequest("POST", url, buf) - if err != nil { - t.Fatalf("failed creating http request: %v", err) - } - req.Header.Set("Content-Type", "application/octet-stream") - - _, err = lp.parseGetProofByHashV1Request(req) - if got, want := err != nil, table.wantErr; got != want { - t.Errorf("got errror %v but wanted %v in test %q: %v", got, want, table.description, err) - } - } -} - -func TestParseGetEntriesV1Request(t *testing.T) { - lp := newLogParameters(t, nil) - for _, table := range []struct { - description string - req *types.GetEntriesV1 - wantErr bool - wantReq *types.GetEntriesV1 - }{ - { - description: "invalid: nothing to unpack", - req: nil, - wantErr: true, - }, - { - description: "invalid: start must be larger than end", - req: &types.GetEntriesV1{Start: 1, End: 0}, - wantErr: true, - }, - { - description: "valid: want truncated range", - req: &types.GetEntriesV1{Start: 0, End: uint64(testdata.MaxRange)}, - wantReq: &types.GetEntriesV1{Start: 0, End: uint64(testdata.MaxRange) - 1}, - }, - { - description: "valid", - req: &types.GetEntriesV1{Start: 0, End: 0}, - wantReq: &types.GetEntriesV1{Start: 0, End: 0}, - }, - } { - var buf *bytes.Buffer - if table.req == nil { - buf = bytes.NewBuffer(nil) - } else { - buf = bytes.NewBuffer(marshal(t, *table.req)) - } - - url := EndpointGetEntries.Path("http://example.com", lp.Prefix) - req, err := http.NewRequest("POST", url, buf) - if err != nil { - t.Fatalf("failed creating http request: %v", err) - } - req.Header.Set("Content-Type", "application/octet-stream") - - output, err := lp.parseGetEntriesV1Request(req) - if got, want := err != nil, table.wantErr; got != want { - t.Errorf("got errror %v but wanted %v in test %q: %v", got, want, table.description, err) - } - if err != nil { - continue - } - if got, want := output, table.wantReq; !reflect.DeepEqual(got, want) { - t.Errorf("got request\n%v\n\tbut wanted\n%v\n\t in test %q", got, want, table.description) - } - } -} - -func TestUnpackOctetPost(t *testing.T) { - for _, table := range []struct { - description string - req *http.Request - out interface{} - wantErr bool - }{ - //{ - // description: "invalid: cannot read request body", - // req: func() *http.Request { - // req, err := http.NewRequest(http.MethodPost, "", iotest.ErrReader(fmt.Errorf("bad reader"))) - // if err != nil { - // t.Fatalf("must make new http request: %v", err) - // } - // return req - // }(), - // out: &types.StItem{}, - // wantErr: true, - //}, // testcase requires Go 1.16 - { - description: "invalid: cannot unmarshal", - req: func() *http.Request { - req, err := http.NewRequest(http.MethodPost, "", bytes.NewBuffer(nil)) - if err != nil { - t.Fatalf("must make new http request: %v", err) - } - return req - }(), - out: &types.StItem{}, - wantErr: true, - }, - { - description: "valid", - req: func() *http.Request { - req, err := http.NewRequest(http.MethodPost, "", bytes.NewBuffer([]byte{0})) - if err != nil { - t.Fatalf("must make new http request: %v", err) - } - return req - }(), - out: &struct{ SomeUint8 uint8 }{}, - }, - } { - err := unpackOctetPost(table.req, table.out) - if got, want := err != nil, table.wantErr; got != want { - t.Errorf("got error %v but wanted %v in test %q", got, want, table.description) - } - } -} - -func marshal(t *testing.T, out interface{}) []byte { - b, err := types.Marshal(out) - if err != nil { - t.Fatalf("must marshal: %v", err) - } - return b -} -- cgit v1.2.3