blob: 6a119b5327587feeac1eaa1ff4972c27e2979e90 [file] [log] [blame]
/* Copyright 2016 The Roughtime Authors.
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* See the License for the specific language governing permissions and
* limitations under the License. */
#include <memory>
#include <utility>
#include "protocol.h"
#include "time_source.h"
namespace roughtime {
// kToBeSignedCertSize is the size of the signed portion (DELE) of a
// certificate. Its tags are (PUBK, MINT, MAXT).
constexpr size_t kToBeSignedCertSize = MessageHeaderLen(3) +
ED25519_PUBLIC_KEY_LEN + kTimestampSize +
// kCertSize is the size of the entire certificate. Its tags are (DELE, SIG).
constexpr size_t kCertSize =
MessageHeaderLen(2) + ED25519_SIGNATURE_LEN + kToBeSignedCertSize;
// CreateCertificate signs the supplied |public_key| using |root_private_key|,
// and sets |out_cert| to a certificate containing the public key, the
// signature, and the supplied validity interval. Returns true if successful,
// otherwise false.
// TODO(mab): Find better home for this, likely in an offline tool.
bool CreateCertificate(uint8_t out_cert[kCertSize],
const uint8_t root_private_key[ED25519_PRIVATE_KEY_LEN],
rough_time_t start_time, rough_time_t end_time,
const uint8_t public_key[ED25519_PUBLIC_KEY_LEN]);
// Identity is a server's private key and certificate. (The certificate is the
// server's public key signed by an offline private master key.)
struct Identity {
uint8_t private_key[ED25519_PRIVATE_KEY_LEN];
uint8_t certificate[kCertSize];
// kBatchSizeLog2 is one less than the number of levels in the Merkle tree of
// client nonces. The response packet must have room for this many hashes.
// This value was taken because it's too easy to saturate batches of size 32 in
// load testing so 64 seems like a reasonable number for now. We may revisit
// this if the non-crypto parts of processing become less expensive.
constexpr size_t kBatchSizeLog2 = 6;
// kBatchSize is the most requests we'll process at a time, a.k.a. the number of
// leaves in the Merkle tree.
constexpr size_t kBatchSize = 1 << kBatchSizeLog2;
// Tree encapsulates the Merkle tree of client nonces. The tree is too large
// (2**13 bytes) to be sent to each client, so each client is sent the part of
// the tree necessary to verify the inclusion of its nonce.
class Tree {
Tree() {}
Tree(const Tree&) = delete;
Tree& operator=(const Tree&) = delete;
// AddLeaf adds a new client nonce at the specified index.
void AddLeaf(size_t index, const uint8_t nonce[kNonceLength]) {
HashLeaf(tree_[0][index], nonce);
// Build constructs the Merkle tree. The existing |num_leaves| leaf hashes in
// levels_[0] are left alone, and the tree is built by (possibly adding a
// dummy leaf node and) creating levels 1 and higher.
// If |num_leaves| is 1, this is a no-op. In that case, the root is the same
// node as the one leaf.
void Build(size_t num_leaves);
// GetPathLength returns the number of nodes necessary to represent a path to
// the root. This may be zero.
size_t GetPathLength() { return levels_ - 1; }
// GetPath sets |*out_path| to the data needed to verify inclusion in the root
// hash of the leaf at |index|. The path consists of one node for each
// sub-root level. So, for example, the first element is the leaf that is the
// sibling of the leaf at |index|.
// The data are intended for consumption by a client that knows the leaf at
// |index|, because it is that client's nonce.
void GetPath(uint8_t* out_path, size_t index);
// GetRoot returns a pointer to the root hash, which is |kNonceLength| bytes
// long.
const uint8_t* GetRoot() { return tree_[levels_ - 1][0]; }
// tree_ is a Merkle tree. The first index is the level of the tree, with
// leaves at level 0.
uint8_t tree_[kBatchSizeLog2 + 1][kBatchSize][kNonceLength];
// Level is the number of levels in the tree, including the root. Hence,
// after calling |Build|, the root lives at |tree_[levels_-1][0]|.
size_t levels_;
constexpr size_t kMaxRecvPacketSize = kMinRequestSize;
constexpr size_t kToBeSignedSize =
MessageHeaderLen(3) + kTimestampSize + kRadiusSize + kNonceLength;
// kMaxResponseSize is the size of the largest possible server response.
constexpr size_t kMaxResponseSize =
MessageHeaderLen(5) + kCertSize + kToBeSignedSize + ED25519_SIGNATURE_LEN +
(kBatchSizeLog2 * kNonceLength) + sizeof(uint32_t) /* index */;
class Server {
Server() = delete;
Server(const Server&) = delete;
Server& operator=(const Server&) = delete;
Server(std::unique_ptr<Identity> identity,
std::unique_ptr<TimeSource> time_source);
// AddRequest decodes |packet|. If the packet is valid, it is added to
// |tree_| and true is returned. Otherwise, false is returned.
bool AddRequest(const uint8_t* packet, size_t len);
// Sign creates a signed response (Merkle tree root and timestamp) and a
// signature.
bool Sign();
// MakeResponse creates a response for the |index|'th leaf node of the Merkle
// tree, where the indices correspond to successful calls to |AddRequest|.
bool MakeResponse(uint8_t* out_response, size_t* out_len, uint32_t index);
void Reset() { num_leaves_ = 0; }
std::unique_ptr<TimeSource> time_source_;
std::unique_ptr<Identity> identity_;
Tree tree_;
// num_leaves is the number of leaf nodes inserted
size_t num_leaves_;
// to_be_signed_with_context_ is the signed portion of the server's response,
// prefixed by |kContextString|.
uint8_t to_be_signed_with_context_[kToBeSignedSize + sizeof(kContextString)];
// to_be_signed_ points |sizeof(kContextStringBytes)| into
// |to_be_signed_with_context_|, for convenience. It contains tags (ROOT,
// TIME).
uint8_t* const to_be_signed_;
// Signature is the ED25519 signature over |to_be_signed_with_context_|.
uint8_t signature_[ED25519_SIGNATURE_LEN];
// BrokenReplyGenerator is an interface for generating replies that are broken
// in a variety of ways. This is used to ensure that clients correctly handle
// various corner cases.
class BrokenReplyGenerator {
virtual ~BrokenReplyGenerator();
// probability_1024 returns the probability, in parts-per-1024, that this
// generator should be used for a given request.
uint16_t probability_1024() const;
void set_probability_1024(uint16_t probabilty);
// Process takes a valid request in |request| and the standard response in
// |normal_response|. If it wishes to substitute an alternative reply then it
// may write up to |max_out_len| bytes to |out|, set |*out_len| to the number
// of bytes written and return true. Otherwise it must return false.
virtual bool Process(uint8_t* out, size_t* out_len, size_t max_out_len,
const uint8_t* normal_response,
size_t normal_response_len, const uint8_t* request,
size_t request_len) = 0;
uint16_t probability_1024_ = 0;
} // namespace roughtime