Skip to content

Instantly share code, notes, and snippets.

@jadeallenx
Last active March 17, 2018 19:40
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save jadeallenx/b29507badc5c8ad3bd61b7d2205c42c7 to your computer and use it in GitHub Desktop.
Save jadeallenx/b29507badc5c8ad3bd61b7d2205c42c7 to your computer and use it in GitHub Desktop.
Using hex v2 resources in Erlang
%% A strawman implementation to kick around
-module(rebar3_hex_v2_resources).
-export([
start/0,
decode_resource_from_disk/2,
download_resource/1
]).
%% needs application:ensure_all_started(public_key).
-include_lib("public_key/include/public_key.hrl").
-define(DIGEST_TYPE, sha512). %% hex currently generates sha512 signatures
-define(HEX_URL, "repo.hex.pm").
-define(HTTP_OPTIONS, []).
-define(REQUEST_OPTIONS, [{body_format, binary}, {full_result, false}]).
-define(HEADERS, []).
-define(LOG(Fmt, Args), io:format(user, Fmt, Args)).
start() ->
A = [ application:ensure_all_started(App) || App <- [inets, public_key, ssl] ],
lists:all(fun({ok, _}) -> true; (_) -> false end, A),
R = [ code:load_file(F) || F <- [hex_pb_names, hex_pb_versions, hex_pb_signed, hex_pb_package] ],
lists:all(fun({module, _Mod}) -> true; (_) -> false end, R),
ok.
download_resource("names") ->
do_download(filename:join(?HEX_URL, "names"));
download_resource("versions") ->
do_download(filename:join(?HEX_URL, "versions"));
download_resource(Pkg) ->
do_download(filename:join([?HEX_URL, "packages", Pkg])).
do_download(BaseUrl) ->
Url = "https://" ++ BaseUrl,
{ok, {Status, Body}} = httpc:request(get, {Url, ?HEADERS}, ?HTTP_OPTIONS, ?REQUEST_OPTIONS),
case Status of
X when X < 300 andalso X > 199 ->
handle_file_write(Url, Body);
_ ->
?LOG("HTTP fetch to ~p failed ~p: ~p", [Url, Status, Body]),
error({error, http_error}, [Url, Status])
end.
handle_file_write(Url, Body) ->
Filename = filename:basename(Url),
ok = file:write_file(Filename, Body),
{Mod, Resource} = resource(Filename),
decode(Mod, Body, Resource).
resource("names") -> {hex_pb_names, 'Names'};
resource("versions") -> {hex_pb_versions, 'Versions'};
resource(_) -> {hex_pb_package, 'Package'}.
decode_resource_from_disk(Type, File) ->
{Mod, Resource} = resource(Type),
do_disk_get(File, Mod, Resource).
do_disk_get(File, Mod, Resource) ->
Raw = get_disk_data(File),
decode(Mod, Raw, Resource).
decode(Mod, Data0, Resource) ->
Data = iolist_to_binary(Data0),
Payload = process_data(Data),
Mod:decode_msg(Payload, Resource).
%% Internal functions
get_disk_data(File) ->
{ok, F} = file:read_file(File),
process_data(F).
process_data(Data) ->
Uncompressed = zlib:gunzip(Data),
Sign = hex_pb_signed:decode_msg(Uncompressed, 'Signed'),
true = validate_signature(Sign),
maps:get(payload, Sign).
validate_signature(M) ->
DataSig = maps:get(signature, M),
Payload = maps:get(payload, M),
Key = case get(hex_pub_key) of
undefined -> load_pub_key("keys/hex_pub.pem");
Val -> Val
end,
public_key:verify(Payload, ?DIGEST_TYPE, DataSig, Key).
load_pub_key(FileLoc) ->
{ok, F} = file:read_file(FileLoc),
Entries = public_key:pem_decode(F),
RSAPubKey = public_key:pem_entry_decode(hd(Entries)),
true = is_record(RSAPubKey, 'RSAPublicKey'),
put(hex_pub_key, RSAPubKey),
RSAPubKey.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment