%% @doc %% Functions for encoding and decoding Hex registries. -module(hex_registry). -export([ encode_names/1, decode_names/2, build_names/2, unpack_names/3, encode_versions/1, decode_versions/2, build_versions/2, unpack_versions/3, encode_package/1, decode_package/3, build_package/2, unpack_package/4, encode_policy/1, decode_policy/3, build_policy/2, unpack_policy/4, sign_protobuf/2, decode_signed/1, decode_and_verify_signed/2, sign/2, verify/3 ]). -include_lib("public_key/include/public_key.hrl"). -type private_key() :: #'RSAPrivateKey'{} | binary(). -type public_key() :: #'RSAPublicKey'{} | binary(). %%==================================================================== %% API functions %%==================================================================== %% @doc %% Builds names resource. build_names(Names, PrivateKey) -> Payload = encode_names(Names), zlib:gzip(sign_protobuf(Payload, PrivateKey)). %% @doc %% Unpacks names resource. unpack_names(Payload, Repository, PublicKey) -> case decode_and_verify_signed(zlib:gunzip(Payload), PublicKey) of {ok, Names} -> decode_names(Names, Repository); Other -> Other end. %% @private encode_names(Names) -> hex_pb_names:encode_msg(Names, 'Names'). %% @private decode_names(Payload, no_verify) -> {ok, hex_pb_names:decode_msg(Payload, 'Names')}; decode_names(Payload, Repository) -> case hex_pb_names:decode_msg(Payload, 'Names') of #{repository := Repository, packages := _Packages} = Result -> {ok, Result}; _ -> {error, bad_repo_name} end. %% @doc %% Builds versions resource. build_versions(Versions, PrivateKey) -> Payload = encode_versions(Versions), zlib:gzip(sign_protobuf(Payload, PrivateKey)). %% @doc %% Unpacks versions resource. unpack_versions(Payload, Repository, PublicKey) -> case decode_and_verify_signed(zlib:gunzip(Payload), PublicKey) of {ok, Versions} -> decode_versions(Versions, Repository); Other -> Other end. %% @private encode_versions(Versions) -> hex_pb_versions:encode_msg(Versions, 'Versions'). %% @private decode_versions(Payload, no_verify) -> {ok, hex_pb_versions:decode_msg(Payload, 'Versions')}; decode_versions(Payload, Repository) -> case hex_pb_versions:decode_msg(Payload, 'Versions') of #{repository := Repository, packages := _Packages} = Result -> {ok, Result}; _ -> {error, bad_repo_name} end. %% @doc %% Builds package resource. build_package(Package, PrivateKey) -> Payload = encode_package(Package), zlib:gzip(sign_protobuf(Payload, PrivateKey)). %% @doc %% Unpacks package resource. unpack_package(Payload, Repository, Name, PublicKey) -> case decode_and_verify_signed(zlib:gunzip(Payload), PublicKey) of {ok, Package} -> decode_package(Package, Repository, Name); Other -> Other end. %% @private encode_package(Package) -> hex_pb_package:encode_msg(Package, 'Package'). %% @private decode_package(Payload, no_verify, no_verify) -> {ok, hex_pb_package:decode_msg(Payload, 'Package')}; decode_package(Payload, Repository, Package) -> case hex_pb_package:decode_msg(Payload, 'Package') of #{repository := Repository, name := Package, releases := _Releases} = Result -> {ok, Result}; _ -> {error, bad_repo_name} end. %% @doc %% Builds policy resource. build_policy(Policy, PrivateKey) -> Payload = encode_policy(Policy), zlib:gzip(sign_protobuf(Payload, PrivateKey)). %% @doc %% Unpacks policy resource. unpack_policy(Payload, Repository, Name, PublicKey) -> case decode_and_verify_signed(zlib:gunzip(Payload), PublicKey) of {ok, Policy} -> decode_policy(Policy, Repository, Name); Other -> Other end. %% @private encode_policy(Policy) -> hex_pb_policy:encode_msg(Policy, 'Policy'). %% @private decode_policy(Payload, no_verify, no_verify) -> {ok, hex_pb_policy:decode_msg(Payload, 'Policy')}; decode_policy(Payload, Repository, Name) -> case hex_pb_policy:decode_msg(Payload, 'Policy') of #{repository := Repository, name := Name} = Result -> {ok, Result}; _ -> {error, bad_repo_name} end. %% @private sign_protobuf(Payload, PrivateKey) -> Signature = sign(Payload, PrivateKey), hex_pb_signed:encode_msg(#{payload => Payload, signature => Signature}, 'Signed'). %% @private decode_signed(Signed) -> hex_pb_signed:decode_msg(Signed, 'Signed'). %% @private -spec decode_and_verify_signed(binary(), public_key()) -> {ok, binary()} | {error, term()}. decode_and_verify_signed(Signed, PublicKey) -> #{payload := Payload, signature := Signature} = decode_signed(Signed), case verify(Payload, Signature, PublicKey) of true -> {ok, Payload}; false -> {error, bad_signature}; {error, Reason} -> {error, Reason} end. %% @private -spec sign(binary(), private_key()) -> binary(). sign(Binary, PrivateKey) -> {ok, RSAPrivateKey} = key(PrivateKey), public_key:sign(Binary, sha512, RSAPrivateKey). %% @private -spec verify(binary(), binary(), public_key()) -> boolean() | {error, term()}. verify(Binary, Signature, PublicKey) -> case key(PublicKey) of {ok, RSAPublicKey} -> public_key:verify(Binary, sha512, Signature, RSAPublicKey); {error, Reason} -> {error, Reason} end. %%==================================================================== %% Internal functions %%==================================================================== %% @private key(#'RSAPublicKey'{} = Key) -> {ok, Key}; key(#'RSAPrivateKey'{} = Key) -> {ok, Key}; key(Binary) when is_binary(Binary) -> case public_key:pem_decode(Binary) of [Entry | _] -> {ok, public_key:pem_entry_decode(Entry)}; _ -> {error, bad_key} end.