%%% Copyright (c) 2014, NORDUnet A/S. %%% See LICENSE for licensing information. %%% @doc Certificate Transparency (RFC 6962) -module(v1). %% API (URL) -export([request/3]). %% Public functions, i.e. part of URL. request(post, "ct/v1/add-chain", Input) -> R = case (catch jiffy:decode(Input)) of {error, E} -> html("add-chain: bad input:", E); {[{<<"chain">>, ChainBase64}]} -> case (catch [base64:decode(X) || X <- ChainBase64]) of {'EXIT', _} -> html("add-chain: invalid base64-encoded chain: ", [ChainBase64]); [LeafCert | CertChain] -> Roots = catlfish:known_roots(), case x509:normalise_chain(Roots, [LeafCert|CertChain]) of {ok, [Leaf | Chain]} -> io:format("[info] adding ~p~n", [x509:cert_string(LeafCert)]), success(catlfish:add_chain(Leaf, Chain)); {error, Reason} -> io:format("[info] rejecting ~p: ~p~n", [x509:cert_string(LeafCert), Reason]), html("add-chain: invalid chain", Reason) end; Invalid -> html("add-chain: chain is not a list: ", [Invalid]) end; _ -> html("add-chain: missing input: chain", Input) end, R; request(post, "ct/v1/add-pre-chain", _Input) -> niy(); request(get, "ct/v1/get-sth", _Query) -> { Treesize, Timestamp, Roothash, Signature} = plop:sth(), R = [{tree_size, Treesize}, {timestamp, Timestamp}, {sha256_root_hash, base64:encode(Roothash)}, {tree_head_signature, base64:encode( plop:serialise(Signature))}], success(jiffy:encode({R})); request(get, "ct/v1/get-sth-consistency", Query) -> R = case lists:sort(Query) of [{"first", FirstInput}, {"second", SecondInput}] -> {First, _} = string:to_integer(FirstInput), {Second, _} = string:to_integer(SecondInput), case lists:member(error, [First, Second]) of true -> html("get-sth-consistency: bad input:", [FirstInput, SecondInput]); false -> success( jiffy:encode( {[{consistency, [base64:encode(X) || X <- plop:consistency(First, Second)]}]})) end; _ -> html("get-sth-consistency: bad input:", Query) end, R; request(get, "ct/v1/get-proof-by-hash", Query) -> R = case lists:sort(Query) of [{"hash", HashInput}, {"tree_size", TreeSizeInput}] -> Hash = case (catch base64:decode(HashInput)) of {'EXIT', _} -> error; H -> H end, {TreeSize, _} = string:to_integer(TreeSizeInput), case lists:member(error, [Hash, TreeSize]) of true -> html("get-proof-by-hash: bad input:", [HashInput, TreeSizeInput]); false -> success( jiffy:encode( case plop:inclusion(Hash, TreeSize) of {ok, Index, Path} -> {[{leaf_index, Index}, {audit_path, [base64:encode(X) || X <- Path]}]}; {notfound, Msg} -> %% FIXME: http status 400 {[{success, false}, {error_message, list_to_binary(Msg)}]} end)) end; _ -> html("get-proof-by-hash: bad input:", Query) end, R; request(get, "ct/v1/get-entries", Query) -> R = case lists:sort(Query) of [{"end", EndInput}, {"start", StartInput}] -> {Start, _} = string:to_integer(StartInput), {End, _} = string:to_integer(EndInput), case lists:member(error, [Start, End]) of true -> html("get-entries: bad input:", [Start, End]); false -> success(catlfish:entries(Start, min(End, Start + 999))) end; _ -> html("get-entries: bad input:", Query) end, R; request(get, "ct/v1/get-entry-and-proof", Query) -> R = case lists:sort(Query) of [{"leaf_index", IndexInput}, {"tree_size", TreeSizeInput}] -> {Index, _} = string:to_integer(IndexInput), {TreeSize, _} = string:to_integer(TreeSizeInput), case lists:member(error, [Index, TreeSize]) of true -> html("get-entry-and-proof: not integers: ", [IndexInput, TreeSizeInput]); false -> success(catlfish:entry_and_proof(Index, TreeSize)) end; _ -> html("get-entry-and-proof: bad input:", Query) end, R; request(get, "ct/v1/get-roots", _Query) -> R = [{certificates, [base64:encode(Der) || Der <- catlfish:update_known_roots()]}], success(jiffy:encode({R})); request(_Method, _Path, _) -> none. %% Private functions. html(Text, Input) -> {400, [{"Content-Type", "text/html"}], io_lib:format( "

~n" ++ "~s~n" ++ "~p~n" ++ "~n", [Text, Input])}. niy() -> html("NIY - Not Implemented Yet|", []). success(Data) -> {200, [{"Content-Type", "text/json"}], Data}.