From 88a9c02a19a9dabc375a40da8c8ed36c44807b65 Mon Sep 17 00:00:00 2001 From: Magnus Ahltorp Date: Fri, 17 Feb 2017 23:31:02 +0100 Subject: Support requirement that storage servers sign stored entries Make SCT cache mandatory. The signature server now requires signatures from the storage nodes, so if SCT is not present in the cache, always send entries to storage servers to collect signatures. Also send SCT when committing entry to storage servers. --- src/catlfish.erl | 69 ++++++++++++++++++++++++++++++-------------------- tools/compileconfig.py | 11 +++++--- 2 files changed, 50 insertions(+), 30 deletions(-) diff --git a/src/catlfish.erl b/src/catlfish.erl index 4bf1cdf..04b3332 100644 --- a/src/catlfish.erl +++ b/src/catlfish.erl @@ -5,7 +5,7 @@ -export([add_chain/3, entries/2, entry_and_proof/2]). -export([known_roots/0, update_known_roots/0]). -export([init_cache_table/0]). --export([entryhash_from_entry/1, verify_entry/1, verify_entry/2]). +-export([entryhash_from_entry/1, verify_entry/1, verify_entry/2, spt_data/1]). -include_lib("eunit/include/eunit.hrl"). -define(PROTOCOL_VERSION, 0). @@ -92,28 +92,22 @@ deserialise_entry_type(<<1:16>>) -> serialise_signature_type(certificate_timestamp) -> <<0:8>>. -calc_sct(TimestampedEntry) -> +spt_data(DBEntry) -> + {_Type, MTLText, _Cert, _Chain} = unpack_entry(DBEntry), + MTL = deserialise_mtl(MTLText), + TSE = MTL#mtl.entry, + sct_data(TSE). + +sct_data(TimestampedEntry) -> + list_to_binary([<>, + serialise_signature_type(certificate_timestamp), + serialise(TimestampedEntry)]). + +calc_sct(TimestampedEntry, Signatures) -> plop:serialise( - plop:spt(list_to_binary([<>, - serialise_signature_type(certificate_timestamp), - serialise(TimestampedEntry)]))). - -get_sct(Hash, TimestampedEntry) -> - case application:get_env(catlfish, sctcache_root_path) of - {ok, RootPath} -> - case perm:readfile(RootPath, Hash) of - Contents when is_binary(Contents) -> - Contents; - noentry -> - SCT = calc_sct(TimestampedEntry), - ok = perm:ensurefile_nosync(RootPath, Hash, SCT), - SCT - end; - _ -> - calc_sct(TimestampedEntry) - end. + plop:spt(sct_data(TimestampedEntry), Signatures)). -add_to_db(Type, LeafCert, CertChain, EntryHash) -> +create_logentry(Type, LeafCert, CertChain) -> EntryType = case Type of normal -> x509_entry; precert -> precert_entry @@ -125,21 +119,34 @@ add_to_db(Type, LeafCert, CertChain, EntryHash) -> entry = TSE}), MTLHash = ht:leaf_hash(MTLText), LogEntry = pack_entry(Type, MTLText, LeafCert, CertChain), - ok = plop:add(LogEntry, MTLHash, EntryHash), - {TSE, MTLHash}. + {TSE, MTLHash, LogEntry}. get_ratelimit_token(Type) -> ratelimit:get_token(Type). + +maybe_add_to_db(Hash, LogEntry, TimestampedEntry, HasEntry) -> + CachedSCTSig = plop:get_spt(Hash), + + case {HasEntry, CachedSCTSig} of + {true, SCT} when is_binary(SCT) -> + SCT; + _ -> + {ok, Signatures} = plop:add(LogEntry, Hash), + SCT = calc_sct(TimestampedEntry, Signatures), + plop:add_spt(Hash, SCT), + SCT + end. + -spec add_chain(binary(), [binary()], normal|precert) -> {[{_,_},...]}. add_chain(LeafCert, CertChain, Type) -> EntryHash = crypto:hash(sha256, [LeafCert | CertChain]), - {TimestampedEntry, Hash} = + {{TimestampedEntry, Hash, LogEntry}, HasEntry} = case plop:get(EntryHash) of notfound -> case get_ratelimit_token(add_chain) of ok -> - add_to_db(Type, LeafCert, CertChain, EntryHash); + {create_logentry(Type, LeafCert, CertChain), false}; _ -> exit({internalerror, "Rate limiting"}) end; @@ -147,10 +154,18 @@ add_chain(LeafCert, CertChain, Type) -> {_Type, MTLText, _Cert, _Chain} = unpack_entry(DBEntry), MTL = deserialise_mtl(MTLText), MTLText = serialise(MTL), % verify FIXME: remove - {MTL#mtl.entry, MTLHash} + {{MTL#mtl.entry, MTLHash, DBEntry}, true} end, - SCT_sig = get_sct(Hash, TimestampedEntry), + SCT_sig = maybe_add_to_db(Hash, LogEntry, TimestampedEntry, HasEntry), + + case HasEntry of + false -> + plop:commit(Hash, EntryHash, SCT_sig); + _ -> + none + end, + {[{sct_version, ?PROTOCOL_VERSION}, {id, base64:encode(plop:get_logid())}, {timestamp, TimestampedEntry#timestamped_entry.timestamp}, diff --git a/tools/compileconfig.py b/tools/compileconfig.py index fd77b90..9973a95 100755 --- a/tools/compileconfig.py +++ b/tools/compileconfig.py @@ -257,8 +257,7 @@ def gen_config(nodename, config, localconfig): if nodetype & set(["frontendnodes", "mergenodes"]): catlfishconfig.append((Symbol("known_roots_path"), localconfig["paths"]["knownroots"])) if "frontendnodes" in nodetype: - if "sctcaching" in options: - catlfishconfig.append((Symbol("sctcache_root_path"), paths["db"] + "sctcache/")) + plopconfig.append((Symbol("sptcache_root_path"), paths["db"] + "sctcache")) if "ratelimits" in localconfig: ratelimits = map(parse_ratelimit, localconfig["ratelimits"].items()) catlfishconfig.append((Symbol("ratelimits"), ratelimits)) @@ -312,6 +311,8 @@ def gen_config(nodename, config, localconfig): (Symbol("sendsth_verified_path"), paths["db"] + "sendsth-verified"), (Symbol("entryhash_from_entry"), (Symbol("catlfish"), Symbol("entryhash_from_entry"))), + (Symbol("spt_data"), + (Symbol("catlfish"), Symbol("spt_data"))), ] if "storagenodes" in nodetype: plopconfig += [ @@ -340,12 +341,14 @@ def gen_config(nodename, config, localconfig): allowed_clients = [] allowed_servers = [] + storagenodenames = [node["name"] for node in config["storagenodes"]] services = set() + storage_sign_quorum = config.get("storage-sign-quorum-size", 0) if "frontendnodes" in nodetype: - storagenodenames = [node["name"] for node in config["storagenodes"]] reloadableplopconfig.append((Symbol("storage_nodes"), storagenodeaddresses)) reloadableplopconfig.append((Symbol("storage_nodes_quorum"), config["storage-quorum-size"])) + reloadableplopconfig.append((Symbol("storage_sign_quorum"), storage_sign_quorum)) services.add(Symbol("ht")) allowed_clients += allowed_clients_frontend(mergenodenames, primarymergenodename) allowed_clients += allowed_clients_public() @@ -353,6 +356,7 @@ def gen_config(nodename, config, localconfig): if "storagenodes" in nodetype: allowed_clients += allowed_clients_storage(frontendnodenames, mergenodenames) if "signingnodes" in nodetype: + reloadableplopconfig.append((Symbol("storage_sign_quorum"), storage_sign_quorum)) allowed_clients += allowed_clients_signing(frontendnodenames, primarymergenodename) services = [Symbol("sign")] if "mergenodes" in nodetype: @@ -409,6 +413,7 @@ def gen_config(nodename, config, localconfig): reloadableplopconfig += [ (Symbol("allowed_clients"), list(allowed_clients)), (Symbol("allowed_servers"), list(allowed_servers)), + (Symbol("storage_node_names"), list(storagenodenames)), (Symbol("apikeys"), apikeys), (Symbol("version"), config["version"]), ] -- cgit v1.1 From bfdce2f5ca404c0c7c5dec43c22fa62b1bd4583f Mon Sep 17 00:00:00 2001 From: Linus Nordberg Date: Tue, 28 Feb 2017 17:06:08 +0100 Subject: Rename a couple functions and variables; add two comments. --- src/catlfish.erl | 25 +++++++++++++++---------- 1 file changed, 15 insertions(+), 10 deletions(-) diff --git a/src/catlfish.erl b/src/catlfish.erl index 04b3332..6d8d430 100644 --- a/src/catlfish.erl +++ b/src/catlfish.erl @@ -103,9 +103,9 @@ sct_data(TimestampedEntry) -> serialise_signature_type(certificate_timestamp), serialise(TimestampedEntry)]). -calc_sct(TimestampedEntry, Signatures) -> +calc_sct_sig(TimestampedEntry, Signatures) -> plop:serialise( - plop:spt(sct_data(TimestampedEntry), Signatures)). + plop:spt_sig(sct_data(TimestampedEntry), Signatures)). create_logentry(Type, LeafCert, CertChain) -> EntryType = case Type of @@ -126,16 +126,21 @@ get_ratelimit_token(Type) -> maybe_add_to_db(Hash, LogEntry, TimestampedEntry, HasEntry) -> - CachedSCTSig = plop:get_spt(Hash), - - case {HasEntry, CachedSCTSig} of - {true, SCT} when is_binary(SCT) -> - SCT; + CachedSCTSig = plop:get_spt_sig(Hash), + HasSig = is_binary(CachedSCTSig), + + case {HasEntry, HasSig} of + {true, true} -> + %% Entry is present in the database and a signature was + %% found in the SCT cache. + CachedSCTSig; _ -> + %% We don't have the entry or we don't have the SCT in the + %% cache. {ok, Signatures} = plop:add(LogEntry, Hash), - SCT = calc_sct(TimestampedEntry, Signatures), - plop:add_spt(Hash, SCT), - SCT + SCT_sig = calc_sct_sig(TimestampedEntry, Signatures), + ok = plop:add_spt_sig(Hash, SCT_sig), + SCT_sig end. -spec add_chain(binary(), [binary()], normal|precert) -> {[{_,_},...]}. -- cgit v1.1 From 59f72f3e2d5668d10c93c32de51e0e23e762ff32 Mon Sep 17 00:00:00 2001 From: Linus Nordberg Date: Tue, 28 Feb 2017 17:07:12 +0100 Subject: Add an embryo for system documentation. Trying ot document the add-chain process. --- doc/system.md | 41 +++++++++++++++++++++++++++++++++++++++++ 1 file changed, 41 insertions(+) create mode 100644 doc/system.md diff --git a/doc/system.md b/doc/system.md new file mode 100644 index 0000000..d5670d5 --- /dev/null +++ b/doc/system.md @@ -0,0 +1,41 @@ +# This document + +This document contains system documentation of catlfish and plop. + +Note that this document is far from complete. Don't draw any +conclusions from missing topics. + +## A certificate chain is being submitted to a frontend node + +External HTTP endpoint ct/v1/add-chain [RFC6962 sect 4.1] has one +input element "chain" which is an array of base64-encoded +certificates. + +The certificate chain is verified and normalised and a "duplicate +check" is done using plop:get() with a hash over the whole chain. If +the entry isn't already present in the database or if a matching SCT +signature is not found in the SCT cache, + +- the entry is added -- plop:add() +- an SCT signature is retrieved from a signing node -- plop:spt\_sig() +- the SCT signature is added to the SCT cache -- plop:add\_spt\_sig() + +If the entry wasn't already present in the database, the entry is +"committed" by calling plop:commit() which calls internal API +storage/entrycommitted on all storage nodes. + +Internal API storage/entrycommitted passes contents of the +"timestamp\_signature" header to plop:add\_spt() which + +- adds the leafhash to the entryhash key-value store, for retrieval of + leafhash given an entry (used in the duplicate check) +- adds the SPT signature to the SPT cache, i.e. the SCT cache for + catlfish + +Internal API storage/sendentry returns a "sig" header with +:. The signature is returned by plop:add() to +catlfish for later use in call to plop:commit(). + +Internal API signing/sct verifies the signatures in the "signatures" +header, counts proper signatures against configured storage sign +quorum and calls its own gen\_server for an SCT signature. -- cgit v1.1 From 088ef3596d44b5c4bd5eec03296ca78fd86b7a88 Mon Sep 17 00:00:00 2001 From: Linus Nordberg Date: Wed, 1 Mar 2017 09:50:48 +0100 Subject: Add a NEWS entry. --- NEWS.md | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/NEWS.md b/NEWS.md index 144aa96..28caec3 100644 --- a/NEWS.md +++ b/NEWS.md @@ -1,3 +1,14 @@ +# Changes in catlfish 1.0.1-alpha-dev + +## Features + +- New configuration option 'storage-sign-quorum-size' determines the + minimum number of storage nodes successfully storing an entry in + order for signing nodes to generate an SCT for it. This prevents a + rouge frontend node from sending out an SCT for an entry that will + never be merged. An effect of this is that the SCT cache is now + mandatory and can not be disabled. + # Changes in catlfish 0.10.0 - 2017-02-11 ## Incompatible changes -- cgit v1.1