Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@
%%%
%%% These functions are abstracted in order to allow for the addition of new
%%% schemes in the future.
-module(dev_httpsig_keyid).
-module(lib_httpsig_keyid).
-export([req_to_key_material/2, keyid_to_committer/1, keyid_to_committer/2]).
-include_lib("include/hb.hrl").

Expand Down
102 changes: 102 additions & 0 deletions src/core/resolver/hb_message.erl
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,7 @@
-export([with_only_committers/2, with_only_committers/3, commitment_devices/2]).
-export([verify/1, verify/2, verify/3, paranoid_verify/2, paranoid_verify/3]).
-export([commit/2, commit/3, signers/2, type/1, minimize/1]).
-export([verified_committers/2, verified_committers/3]).
-export([normalize_commitments/2, normalize_commitments/3, is_signed_key/3]).
-export([commitment/2, commitment/3, commitments/3]).
-export([with_only_committed/2, without_unless_signed/3]).
Expand Down Expand Up @@ -673,6 +674,107 @@ signers(Msg, Opts) ->
hb_ao:raw(<<"message@1.0">>, <<"committers">>, Msg, #{}, Opts)
).

%% @doc Return committers whose commitments verify and cover the required keys.
verified_committers(Request, Opts) ->
verified_committers(Request, [], Opts).

verified_committers(Request, RequiredKeys, Opts) ->
Commitments = hb_maps:get(<<"commitments">>, Request, #{}, Opts),
lists:filtermap(
fun({CommitmentID, Commitment}) ->
LoadedCommitment = hb_cache:ensure_all_loaded(Commitment, Opts),
case valid_commitment(
Request,
CommitmentID,
LoadedCommitment,
RequiredKeys,
Opts
) of
{true, Committer} -> {true, Committer};
false -> false
end
end,
hb_maps:to_list(Commitments, Opts)
).

valid_commitment(
Request,
CommitmentID,
Commitment =
#{
<<"commitment-device">> := _CommitmentDevice
},
RequiredKeys,
Opts) ->
Committed = commitment_keys(Commitment, Opts),
Present = committed_keys_present(Committed, RequiredKeys),
case Present andalso verify_commitment(Request, CommitmentID, Opts) of
true ->
case verified_committer(Commitment, Opts) of
undefined -> false;
Committer -> {true, Committer}
end;
false -> false
end;
valid_commitment(_Request, _CommitmentID, _Commitment, _RequiredKeys, _Opts) ->
false.

%% @doc Return the verified committer identity implied by a commitment.
verified_committer(Commitment, Opts) ->
case hb_maps:get(<<"keyid">>, Commitment, undefined, Opts) of
undefined ->
hb_maps:get(<<"committer">>, Commitment, undefined, Opts);
KeyID ->
case {
hb_maps:get(<<"commitment-device">>, Commitment, undefined, Opts),
hb_maps:get(<<"type">>, Commitment, undefined, Opts)
} of
{<<"ans104@1.0">>, Type} when Type =/= undefined ->
ans104_keyid_to_committer(KeyID, Type);
_ ->
lib_httpsig_keyid:keyid_to_committer(KeyID)
end
end.

%% @doc Return the committer implied by an ANS-104 keyid and signature type.
ans104_keyid_to_committer(<<"publickey:", _/binary>> = KeyID, Type) ->
try
hb_util:human_id(
ar_wallet:to_address(
hb_util:decode(hb_util:remove_scheme_prefix(KeyID)),
ar_tx:deserialize_sig_type(Type)
)
)
catch _:_ ->
undefined
end;
ans104_keyid_to_committer(KeyID, _Type) ->
lib_httpsig_keyid:keyid_to_committer(KeyID).

committed_keys_present(Committed, RequiredKeys) ->
lists:all(
fun(Key) -> lists:member(Key, Committed) end,
RequiredKeys
).

verify_commitment(Request, CommitmentID, Opts) ->
try
{ok, VerificationBase} = hb_message:with_only_committed(Request, Opts),
hb_message:verify(
VerificationBase,
#{ <<"commitment-ids">> => [CommitmentID] },
Opts
)
catch
_:_ -> false
end.

commitment_keys(Commitment, Opts) ->
RawCommitted = maps:get(<<"committed">>, Commitment, []),
try hb_util:message_to_ordered_list(RawCommitted, Opts)
catch _:_ -> RawCommitted
end.

%% @doc Pretty-print a message.
print(Msg) -> print(Msg, 0).
print(Msg, Indent) ->
Expand Down
6 changes: 3 additions & 3 deletions src/preloaded/codec/dev_httpsig.erl
Original file line number Diff line number Diff line change
Expand Up @@ -85,7 +85,7 @@ verify(Base, Req, RawOpts) ->
Opts = opts(RawOpts),
{ok, EncMsg, EncComm, _} = normalize_for_encoding(Base, Req, Opts),
SigBase = signature_base(EncMsg, EncComm, Opts),
KeyRes = dev_httpsig_keyid:req_to_key_material(Req, Opts),
KeyRes = lib_httpsig_keyid:req_to_key_material(Req, Opts),
RawSignature = hb_util:decode(Signature = maps:get(<<"signature">>, Req)),
?event_debug(debug_httpsig,
{
Expand Down Expand Up @@ -211,8 +211,8 @@ commit(BaseMsg, Req = #{ <<"type">> := <<"hmac-sha256">> }, RawOpts) ->
% Extract the key material from the request.
Opts = opts(RawOpts),
?event_debug({req_to_key_material, {priv_req, Req}}),
{ok, Scheme, Key, KeyID} = dev_httpsig_keyid:req_to_key_material(Req, Opts),
Committer = dev_httpsig_keyid:keyid_to_committer(Scheme, KeyID),
{ok, Scheme, Key, KeyID} = lib_httpsig_keyid:req_to_key_material(Req, Opts),
Committer = lib_httpsig_keyid:keyid_to_committer(Scheme, KeyID),
% Remove any existing hmac commitments with the given keyid before adding
% the new one.
Msg =
Expand Down
2 changes: 1 addition & 1 deletion src/preloaded/codec/dev_httpsig_siginfo.erl
Original file line number Diff line number Diff line change
Expand Up @@ -294,7 +294,7 @@ sf_siginfo_to_commitment(Msg, BodyKeys, SFSig, SFSigInput, Opts) ->
end,
KeyID = maps:get(<<"keyid">>, Commitment4, <<>>),
Commitment5 =
case dev_httpsig_keyid:keyid_to_committer(KeyID) of
case lib_httpsig_keyid:keyid_to_committer(KeyID) of
undefined ->
Commitment4;
Committer ->
Expand Down
42 changes: 42 additions & 0 deletions src/preloaded/message/dev_message.erl
Original file line number Diff line number Diff line change
Expand Up @@ -1005,6 +1005,48 @@ test_verify(KeyType) ->
)
).

verified_committers_supports_httpsig_and_ed25519_test() ->
Msg = #{ <<"a">> => <<"b">> },
RequiredKeys = [<<"a">>],
HTTPSigWallet = ar_wallet:new(?RSA_KEY_TYPE),
HTTPSigSigned =
hb_message:commit(
Msg,
#{ <<"priv-wallet">> => HTTPSigWallet },
#{ <<"commitment-device">> => <<"httpsig@1.0">> }
),
HTTPSigCommitters =
hb_message:verified_committers(HTTPSigSigned, RequiredKeys, #{}),
?assert(
lists:member(
hb_util:human_id(ar_wallet:to_address(HTTPSigWallet)),
HTTPSigCommitters
)
),
Ed25519Wallet = ar_wallet:new(?EDDSA_KEY_TYPE),
Ed25519Signed =
hb_message:commit(
Msg,
#{ <<"priv-wallet">> => Ed25519Wallet },
#{
<<"commitment-device">> => <<"ans104@1.0">>,
<<"type">> => ?EDDSA_SIGN_TYPE
}
),
Ed25519Committers =
hb_message:verified_committers(Ed25519Signed, RequiredKeys, #{}),
?assert(
lists:member(
hb_util:human_id(
ar_wallet:to_address(
ar_wallet:to_pubkey(Ed25519Wallet),
?EDDSA_KEY_TYPE
)
),
Ed25519Committers
)
).

set_nested_link_test() ->
Opts = #{ <<"store">> => [hb_test_utils:test_store(hb_store_lmdb)] },

Expand Down
Loading