summaryrefslogtreecommitdiff
path: root/src/catlfish.erl
diff options
context:
space:
mode:
Diffstat (limited to 'src/catlfish.erl')
-rw-r--r--src/catlfish.erl234
1 files changed, 168 insertions, 66 deletions
diff --git a/src/catlfish.erl b/src/catlfish.erl
index 3956eec..2e5ffd4 100644
--- a/src/catlfish.erl
+++ b/src/catlfish.erl
@@ -2,7 +2,7 @@
%%% See LICENSE for licensing information.
-module(catlfish).
--export([add_chain/2, entries/2, entry_and_proof/2]).
+-export([add_chain/3, entries/2, entry_and_proof/2]).
-export([known_roots/0, update_known_roots/0]).
-export([init_cache_table/0]).
-include_lib("eunit/include/eunit.hrl").
@@ -21,99 +21,202 @@
-record(timestamped_entry, {timestamp :: integer(),
entry_type :: entry_type(),
- signed_entry :: binary(),
+ signed_entry :: signed_x509_entry() |
+ signed_precert_entry(),
extensions = <<>> :: binary()}).
-type timestamped_entry() :: #timestamped_entry{}.
--spec serialise(mtl() | timestamped_entry()) -> binary().
-serialise(#timestamped_entry{timestamp = Timestamp} = E) ->
- list_to_binary(
- [<<Timestamp:64>>,
- serialise_entry_type(E#timestamped_entry.entry_type),
- encode_tls_vector(E#timestamped_entry.signed_entry, 3),
- encode_tls_vector(E#timestamped_entry.extensions, 2)]);
+-record(signed_x509_entry, {asn1_cert :: binary()}).
+-type signed_x509_entry() :: #signed_x509_entry{}.
+-record(signed_precert_entry, {issuer_key_hash :: binary(),
+ tbs_certificate :: binary()}).
+-type signed_precert_entry() :: #signed_precert_entry{}.
+
+-spec serialise(mtl() | timestamped_entry() |
+ signed_x509_entry() | signed_precert_entry()) -> binary().
+%% @doc Serialise a MerkleTreeLeaf as per RFC6962 Section 3.4.
serialise(#mtl{leaf_version = LeafVersion,
leaf_type = LeafType,
entry = TimestampedEntry}) ->
list_to_binary(
[serialise_leaf_version(LeafVersion),
serialise_leaf_type(LeafType),
- serialise(TimestampedEntry)]).
+ serialise(TimestampedEntry)]);
+%% @doc Serialise a TimestampedEntry as per RFC6962 Section 3.4.
+serialise(#timestamped_entry{timestamp = Timestamp,
+ entry_type = EntryType,
+ signed_entry = SignedEntry,
+ extensions = Extensions}) ->
+ list_to_binary(
+ [<<Timestamp:64>>,
+ serialise_entry_type(EntryType),
+ serialise(SignedEntry),
+ encode_tls_vector(Extensions, 2)]);
+%% @doc Serialise an ASN1.Cert as per RFC6962 Section 3.1.
+serialise(#signed_x509_entry{asn1_cert = Cert}) ->
+ encode_tls_vector(Cert, 3);
+%% @doc Serialise a PreCert as per RFC6962 Section 3.2.
+serialise(#signed_precert_entry{
+ issuer_key_hash = IssuerKeyHash,
+ tbs_certificate = TBSCertificate}) when is_binary(IssuerKeyHash),
+ size(IssuerKeyHash) == 32 ->
+ list_to_binary(
+ [IssuerKeyHash,
+ encode_tls_vector(TBSCertificate, 3)]).
serialise_leaf_version(v1) ->
<<0:8>>;
serialise_leaf_version(v2) ->
<<1:8>>.
+deserialise_leaf_version(<<0:8>>) ->
+ v1;
+deserialise_leaf_version(<<1:8>>) ->
+ v2.
serialise_leaf_type(timestamped_entry) ->
<<0:8>>.
-%% serialise_leaf_type(_) ->
-%% <<>>.
+deserialise_leaf_type(<<0:8>>) ->
+ timestamped_entry.
serialise_entry_type(x509_entry) ->
<<0:16>>;
serialise_entry_type(precert_entry) ->
<<1:16>>.
+deserialise_entry_type(<<0:16>>) ->
+ x509_entry;
+deserialise_entry_type(<<1:16>>) ->
+ precert_entry.
+-spec serialise_signature_type(certificate_timestamp|tree_hash) -> binary().
serialise_signature_type(certificate_timestamp) ->
<<0:8>>;
serialise_signature_type(tree_hash) ->
<<1:8>>.
-build_mtl(Timestamp, LeafCert) ->
- TSE = #timestamped_entry{timestamp = Timestamp,
- entry_type = x509_entry,
- signed_entry = LeafCert},
- MTL = #mtl{leaf_version = v1,
- leaf_type = timestamped_entry,
- entry = TSE},
- serialise(MTL).
-
--spec add_chain(binary(), [binary()]) -> nonempty_string().
-add_chain(LeafCert, CertChain) ->
+calc_sct(TimestampedEntry) ->
+ plop:serialise(
+ plop:spt(list_to_binary([<<?PROTOCOL_VERSION:8>>,
+ 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.
+
+-spec add_chain(binary(), [binary()], normal|precert) -> nonempty_string().
+add_chain(LeafCert, CertChain, Type) ->
EntryHash = crypto:hash(sha256, [LeafCert | CertChain]),
- TimestampedEntry =
+ EntryType = case Type of
+ normal -> x509_entry;
+ precert -> precert_entry
+ end,
+ {TimestampedEntry, Hash} =
case plop:get(EntryHash) of
notfound ->
Timestamp = plop:generate_timestamp(),
- TSE = #timestamped_entry{timestamp = Timestamp,
- entry_type = x509_entry,
- signed_entry = LeafCert},
- MTL = #mtl{leaf_version = v1,
- leaf_type = timestamped_entry,
- entry = TSE},
- ok = plop:add(
- serialise_logentry(Timestamp, LeafCert, CertChain),
- ht:leaf_hash(serialise(MTL)),
- EntryHash),
- TSE;
- {_Index, _MTLHash, Entry} ->
- <<Timestamp:64, _LogEntry/binary>> = Entry,
- %% TODO: Perform a costly db consistency check against
- %% unpacked LogEntry (w/ LeafCert and CertChain)
- #timestamped_entry{timestamp = Timestamp,
- entry_type = x509_entry,
- signed_entry = LeafCert}
+ TSE = timestamped_entry(Timestamp, EntryType, LeafCert, CertChain),
+ MTLText = serialise(#mtl{leaf_version = v1,
+ leaf_type = timestamped_entry,
+ entry = TSE}),
+ MTLHash = ht:leaf_hash(MTLText),
+ ExtraData =
+ case Type of
+ normal -> CertChain;
+ precert -> [LeafCert | CertChain]
+ end,
+ LogEntry =
+ list_to_binary(
+ [encode_tls_vector(MTLText, 4),
+ encode_tls_vector(
+ encode_tls_vector(
+ list_to_binary(
+ [encode_tls_vector(C, 3) || C <- ExtraData]),
+ 3),
+ 4)]),
+ ok = plop:add(LogEntry, MTLHash, EntryHash),
+ {TSE, MTLHash};
+ {_Index, MTLHash, DBEntry} ->
+ {MTLText, _ExtraData} = unpack_entry(DBEntry),
+ MTL = deserialise_mtl(MTLText),
+ MTLText = serialise(MTL), % verify FIXME: remove
+ {MTL#mtl.entry, MTLHash}
end,
- SCT_sig =
- plop:spt(list_to_binary([<<?PROTOCOL_VERSION:8>>,
- serialise_signature_type(certificate_timestamp),
- serialise(TimestampedEntry)])),
+
+ SCT_sig = get_sct(Hash, TimestampedEntry),
{[{sct_version, ?PROTOCOL_VERSION},
{id, base64:encode(plop:get_logid())},
{timestamp, TimestampedEntry#timestamped_entry.timestamp},
{extensions, base64:encode(<<>>)},
- {signature, base64:encode(plop:serialise(SCT_sig))}]}.
+ {signature, base64:encode(SCT_sig)}]}.
--spec serialise_logentry(integer(), binary(), [binary()]) -> binary().
-serialise_logentry(Timestamp, LeafCert, CertChain) ->
- list_to_binary(
- [<<Timestamp:64>>,
- list_to_binary(
- [encode_tls_vector(LeafCert, 3),
- encode_tls_vector(
- list_to_binary(
- [encode_tls_vector(X, 3) || X <- CertChain]), 3)])]).
+-spec timestamped_entry(integer(), entry_type(), binary(), binary()) ->
+ timestamped_entry().
+timestamped_entry(Timestamp, EntryType, LeafCert, CertChain) ->
+ SignedEntry =
+ case EntryType of
+ x509_entry ->
+ #signed_x509_entry{asn1_cert = LeafCert};
+ precert_entry ->
+ {DetoxedLeafTBSCert, IssuerKeyHash} =
+ x509:detox(LeafCert, CertChain),
+ #signed_precert_entry{
+ issuer_key_hash = IssuerKeyHash,
+ tbs_certificate = DetoxedLeafTBSCert}
+ end,
+ #timestamped_entry{timestamp = Timestamp,
+ entry_type = EntryType,
+ signed_entry = SignedEntry}.
+
+-spec deserialise_mtl(binary()) -> mtl().
+deserialise_mtl(Data) ->
+ <<LeafVersionBin:1/binary,
+ LeafTypeBin:1/binary,
+ TimestampedEntryBin/binary>> = Data,
+ #mtl{leaf_version = deserialise_leaf_version(LeafVersionBin),
+ leaf_type = deserialise_leaf_type(LeafTypeBin),
+ entry = deserialise_timestampedentry(TimestampedEntryBin)}.
+
+-spec deserialise_timestampedentry(binary()) -> timestamped_entry().
+deserialise_timestampedentry(Data) ->
+ <<Timestamp:64, EntryTypeBin:2/binary, RestData/binary>> = Data,
+ EntryType = deserialise_entry_type(EntryTypeBin),
+ {SignedEntry, ExtensionsBin} =
+ case EntryType of
+ x509_entry ->
+ deserialise_signed_x509_entry(RestData);
+ precert_entry ->
+ deserialise_signed_precert_entry(RestData)
+ end,
+ {Extensions, <<>>} = decode_tls_vector(ExtensionsBin, 2),
+ #timestamped_entry{timestamp = Timestamp,
+ entry_type = EntryType,
+ signed_entry = SignedEntry,
+ extensions = Extensions}.
+
+-spec deserialise_signed_x509_entry(binary()) -> {signed_x509_entry(), binary()}.
+deserialise_signed_x509_entry(Data) ->
+ {E, D} = decode_tls_vector(Data, 3),
+ {#signed_x509_entry{asn1_cert = E}, D}.
+
+-spec deserialise_signed_precert_entry(binary()) ->
+ {signed_precert_entry(), binary()}.
+deserialise_signed_precert_entry(Data) ->
+ <<IssuerKeyHash:32/binary, RestData/binary>> = Data,
+ {TBSCertificate, RestData2} = decode_tls_vector(RestData, 3),
+ {#signed_precert_entry{issuer_key_hash = IssuerKeyHash,
+ tbs_certificate = TBSCertificate},
+ RestData2}.
-spec entries(non_neg_integer(), non_neg_integer()) -> list().
entries(Start, End) ->
@@ -123,10 +226,9 @@ entries(Start, End) ->
entry_and_proof(Index, TreeSize) ->
case plop:inclusion_and_entry(Index, TreeSize) of
{ok, Entry, Path} ->
- {Timestamp, LeafCertVector, CertChainVector} = unpack_entry(Entry),
- MTL = build_mtl(Timestamp, LeafCertVector),
+ {MTL, ExtraData} = unpack_entry(Entry),
{[{leaf_input, base64:encode(MTL)},
- {extra_data, base64:encode(CertChainVector)},
+ {extra_data, base64:encode(ExtraData)},
{audit_path, [base64:encode(X) || X <- Path]}]};
{notfound, Msg} ->
{[{success, false},
@@ -142,20 +244,20 @@ init_cache_table() ->
ets:new(?CACHE_TABLE, [set, public, named_table]).
%% Private functions.
+-spec unpack_entry(binary()) -> {binary(), binary()}.
unpack_entry(Entry) ->
- <<Timestamp:64, LogEntry/binary>> = Entry,
- {LeafCertVector, CertChainVector} = decode_tls_vector(LogEntry, 3),
- {Timestamp, LeafCertVector, CertChainVector}.
+ {MTL, Rest} = decode_tls_vector(Entry, 4),
+ {ExtraData, <<>>} = decode_tls_vector(Rest, 4),
+ {MTL, ExtraData}.
-spec x_entries([{non_neg_integer(), binary(), binary()}]) -> list().
x_entries([]) ->
[];
x_entries([H|T]) ->
{_Index, _Hash, Entry} = H,
- {Timestamp, LeafCertVector, CertChainVector} = unpack_entry(Entry),
- MTL = build_mtl(Timestamp, LeafCertVector),
- [{[{leaf_input, base64:encode(MTL)}, {extra_data, base64:encode(CertChainVector)}]} |
- x_entries(T)].
+ {MTL, ExtraData} = unpack_entry(Entry),
+ [{[{leaf_input, base64:encode(MTL)},
+ {extra_data, base64:encode(ExtraData)}]} | x_entries(T)].
-spec encode_tls_vector(binary(), non_neg_integer()) -> binary().
encode_tls_vector(Binary, LengthLen) ->