wip
This commit is contained in:
parent
1e6132f7e5
commit
f20f205c4f
@ -64,7 +64,7 @@
|
|||||||
type := app | lib}.
|
type := app | lib}.
|
||||||
|
|
||||||
-type outcome() :: ok
|
-type outcome() :: ok
|
||||||
| {error, Reason :: atom()}
|
| {error, Reason :: term()}
|
||||||
| {error, Code :: non_neg_integer()}
|
| {error, Code :: non_neg_integer()}
|
||||||
| {error, Info :: string(), Code :: non_neg_integer()}.
|
| {error, Info :: string(), Code :: non_neg_integer()}.
|
||||||
|
|
||||||
@ -100,8 +100,10 @@ do(["list", "deps"]) ->
|
|||||||
done(zx_local:list_deps());
|
done(zx_local:list_deps());
|
||||||
do(["list", "deps", PackageString]) ->
|
do(["list", "deps", PackageString]) ->
|
||||||
done(zx_local:list_deps(PackageString));
|
done(zx_local:list_deps(PackageString));
|
||||||
do(["install", PackageFile]) ->
|
do(["import", "zrp", PackageFile]) ->
|
||||||
done(zx_local:assimilate(PackageFile));
|
done(zx_daemon:import_zrp(PackageFile));
|
||||||
|
do(["install", PackageString]) ->
|
||||||
|
done(zx_daemon:install(PackageString));
|
||||||
do(["set", "dep", PackageString]) ->
|
do(["set", "dep", PackageString]) ->
|
||||||
done(zx_local:set_dep(PackageString));
|
done(zx_local:set_dep(PackageString));
|
||||||
do(["set", "version", VersionString]) ->
|
do(["set", "version", VersionString]) ->
|
||||||
@ -331,7 +333,7 @@ run(Identifier, RunArgs) ->
|
|||||||
end,
|
end,
|
||||||
{ok, PackageID} = ensure_installed(FuzzyID),
|
{ok, PackageID} = ensure_installed(FuzzyID),
|
||||||
ok = build(PackageID),
|
ok = build(PackageID),
|
||||||
Dir = zx_lib:path(lib, PackageID),
|
Dir = zx_lib:ppath(lib, PackageID),
|
||||||
{ok, Meta} = zx_lib:read_project_meta(Dir),
|
{ok, Meta} = zx_lib:read_project_meta(Dir),
|
||||||
prepare(PackageID, Meta, Dir, RunArgs).
|
prepare(PackageID, Meta, Dir, RunArgs).
|
||||||
|
|
||||||
@ -527,46 +529,13 @@ tuplize(String, Acc) ->
|
|||||||
end.
|
end.
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
%%% Package utilities
|
|
||||||
|
|
||||||
|
|
||||||
-spec install(package_id()) -> ok.
|
|
||||||
%% @private
|
|
||||||
%% Install a package from the cache into the local system.
|
|
||||||
%% Before calling this function it must be known that:
|
|
||||||
%% - The zsp file is in the cache
|
|
||||||
%% - The zsp file is valid
|
|
||||||
%% - This function will only be called on startup by the launch process
|
|
||||||
%% - The package is not already installed
|
|
||||||
%% - If this function crashes it will completely halt the system
|
|
||||||
|
|
||||||
install(PackageID = {Realm, Name, _}) ->
|
|
||||||
{ok, PackageString} = zx_lib:package_string(PackageID),
|
|
||||||
ok = log(info, "Installing ~ts", [PackageString]),
|
|
||||||
ZrpFile = filename:join(zx_lib:path(zsp, Realm, Name), zx_lib:namify_zsp(PackageID)),
|
|
||||||
Files = zx_lib:extract_zsp_or_die(ZrpFile),
|
|
||||||
TgzFile = zx_lib:namify_tgz(PackageID),
|
|
||||||
{TgzFile, TgzData} = lists:keyfind(TgzFile, 1, Files),
|
|
||||||
{"zomp.meta", MetaBin} = lists:keyfind("zomp.meta", 1, Files),
|
|
||||||
Meta = binary_to_term(MetaBin),
|
|
||||||
{KeyID, Signature} = maps:get(sig, Meta),
|
|
||||||
{ok, PubKey} = zx_key:load(public, KeyID),
|
|
||||||
ok = ensure_package_dirs(PackageID),
|
|
||||||
PackageDir = zx_lib:path(lib, PackageID),
|
|
||||||
ok = zx_lib:force_dir(PackageDir),
|
|
||||||
ok = zx_key:verify(TgzData, Signature, PubKey),
|
|
||||||
ok = erl_tar:extract({binary, TgzData}, [compressed, {cwd, PackageDir}]),
|
|
||||||
log(info, "~ts installed", [PackageString]).
|
|
||||||
|
|
||||||
|
|
||||||
-spec build(package_id()) -> ok.
|
-spec build(package_id()) -> ok.
|
||||||
%% @private
|
%% @private
|
||||||
%% Given an AppID, build the project from source and add it to the current lib path.
|
%% Given an AppID, build the project from source and add it to the current lib path.
|
||||||
|
|
||||||
build(PackageID) ->
|
build(PackageID) ->
|
||||||
{ok, CWD} = file:get_cwd(),
|
{ok, CWD} = file:get_cwd(),
|
||||||
ok = file:set_cwd(zx_lib:path(lib, PackageID)),
|
ok = file:set_cwd(zx_lib:ppath(lib, PackageID)),
|
||||||
ok = zx_lib:build(),
|
ok = zx_lib:build(),
|
||||||
file:set_cwd(CWD).
|
file:set_cwd(CWD).
|
||||||
|
|
||||||
@ -577,7 +546,7 @@ build(PackageID) ->
|
|||||||
%% run have been created or halt execution.
|
%% run have been created or halt execution.
|
||||||
|
|
||||||
ensure_package_dirs(PackageID) ->
|
ensure_package_dirs(PackageID) ->
|
||||||
Dirs = [zx_lib:path(D, PackageID) || D <- [etc, var, tmp, log, lib]],
|
Dirs = [zx_lib:ppath(D, PackageID) || D <- [etc, var, tmp, log, lib]],
|
||||||
lists:foreach(fun zx_lib:force_dir/1, Dirs).
|
lists:foreach(fun zx_lib:force_dir/1, Dirs).
|
||||||
|
|
||||||
|
|
||||||
@ -642,10 +611,9 @@ usage() ->
|
|||||||
" zx reject PackageID~n"
|
" zx reject PackageID~n"
|
||||||
" zx add key Realm KeyName~n"
|
" zx add key Realm KeyName~n"
|
||||||
" zx get key Realm KeyName~n"
|
" zx get key Realm KeyName~n"
|
||||||
" zx rem key Realm KeyName~n"
|
|
||||||
" zx create user~n"
|
" zx create user~n"
|
||||||
" zx create userfiles Realm UserName~n"
|
" zx create userfiles Realm UserName~n"
|
||||||
" zx create keypair Realm~n"
|
" zx create keypair~n"
|
||||||
" zx export user UserID~n"
|
" zx export user UserID~n"
|
||||||
" zx import user ZdufFile~n"
|
" zx import user ZdufFile~n"
|
||||||
"~n"
|
"~n"
|
||||||
@ -658,7 +626,6 @@ usage() ->
|
|||||||
" zx accept PackageID~n"
|
" zx accept PackageID~n"
|
||||||
" zx create realm~n"
|
" zx create realm~n"
|
||||||
" zx create realmfile Realm~n"
|
" zx create realmfile Realm~n"
|
||||||
" zx create sysop~n"
|
|
||||||
"~n"
|
"~n"
|
||||||
"Where~n"
|
"Where~n"
|
||||||
" PackageID :: A string of the form Realm-Name[-Version]~n"
|
" PackageID :: A string of the form Realm-Name[-Version]~n"
|
||||||
|
|||||||
@ -91,26 +91,24 @@ list_resigns(Realm) ->
|
|||||||
end.
|
end.
|
||||||
|
|
||||||
|
|
||||||
-spec submit(PackageFile) -> no_return()
|
-spec submit(ZspPath :: file:filename()) -> zx:outcome().
|
||||||
when PackageFile :: file:filename().
|
|
||||||
%% @private
|
%% @private
|
||||||
%% Submit a package to the appropriate "prime" server for the given realm.
|
%% Submit a package to the appropriate "prime" server for the given realm.
|
||||||
|
|
||||||
submit(PackageFile) ->
|
submit(ZspPath) ->
|
||||||
Files = zx_lib:extract_zsp_or_die(PackageFile),
|
{ok, ZspBin} = file:read_file(ZspPath),
|
||||||
{ok, PackageData} = file:read_file(PackageFile),
|
<<SigSize:24, Sig:SigSize/binary, Signed/binary>> = ZspBin,
|
||||||
{"zomp.meta", MetaBin} = lists:keyfind("zomp.meta", 1, Files),
|
<<MetaSize:16, MetaBin:MetaSize/binary, TarGz/binary>> = Signed,
|
||||||
Meta = binary_to_term(MetaBin),
|
{ok, {PackageID, SigKeyName}} = zx_lib:b_to_ts(MetaBin),
|
||||||
{Realm, Package, Version} = maps:get(package_id, Meta),
|
{ok, PubKey} = zx_key:load(public, {element(1, PackageID), SigKeyName}),
|
||||||
{ok, Socket} = connect_auth(Realm),
|
true = zx_key:verify(Signed, Sig, PubKey),
|
||||||
ok = send(Socket, {submit, {Realm, Package, Version}}),
|
ok = send(Socket, {submit, PackageID}),
|
||||||
ok = recv_or_die(Socket),
|
ok = recv_or_die(Socket),
|
||||||
ok = gen_tcp:send(Socket, PackageData),
|
ok = gen_tcp:send(Socket, ZspBin),
|
||||||
ok = log(info, "Done sending contents of ~tp", [PackageFile]),
|
ok = log(info, "Done sending contents of ~tp", [PackageFile]),
|
||||||
Outcome = recv_or_die(Socket),
|
Outcome = recv_or_die(Socket),
|
||||||
log(info, "Response: ~tp", [Outcome]),
|
log(info, "Response: ~tp", [Outcome]),
|
||||||
ok = disconnect(Socket),
|
disconnect(Socket).
|
||||||
halt(0).
|
|
||||||
|
|
||||||
|
|
||||||
review(PackageString) ->
|
review(PackageString) ->
|
||||||
|
|||||||
@ -148,6 +148,7 @@
|
|||||||
subscribe/1, unsubscribe/1,
|
subscribe/1, unsubscribe/1,
|
||||||
list/0, list/1, list/2, list/3, latest/1,
|
list/0, list/1, list/2, list/3, latest/1,
|
||||||
fetch/1, verify_key/1,
|
fetch/1, verify_key/1,
|
||||||
|
install/1, import_zsp/1,
|
||||||
pending/1, packagers/1, maintainers/1, sysops/1]).
|
pending/1, packagers/1, maintainers/1, sysops/1]).
|
||||||
-export([report/1, result/2, notify/2]).
|
-export([report/1, result/2, notify/2]).
|
||||||
-export([start_link/0, stop/0]).
|
-export([start_link/0, stop/0]).
|
||||||
@ -485,6 +486,27 @@ verify_key({Realm, KeyName}) ->
|
|||||||
request({verify_key, Realm, KeyName}).
|
request({verify_key, Realm, KeyName}).
|
||||||
|
|
||||||
|
|
||||||
|
-spec import_zsp(Path :: file:filename()) -> zx:outcome().
|
||||||
|
%% @doc
|
||||||
|
%% Install a package from a local file.
|
||||||
|
|
||||||
|
import_zsp(Path) ->
|
||||||
|
gen_server:call(?MODULE, {import_zsp, Path}).
|
||||||
|
|
||||||
|
|
||||||
|
-spec install(PackageString :: string()) -> zx:outcome().
|
||||||
|
%% @doc
|
||||||
|
%% Install the specified package.
|
||||||
|
|
||||||
|
install(PackageString) ->
|
||||||
|
case zx_lib:package_id(PackageString) of
|
||||||
|
{ok, PackageID} ->
|
||||||
|
gen_server:call(?MODULE, {install, PackageID});
|
||||||
|
{error, invalid_package_string} ->
|
||||||
|
{error, "Invalid package string. Try again.", 22}
|
||||||
|
end.
|
||||||
|
|
||||||
|
|
||||||
-spec pending(Package) -> {ok, RequestID}
|
-spec pending(Package) -> {ok, RequestID}
|
||||||
when Package :: zx:package(),
|
when Package :: zx:package(),
|
||||||
RequestID :: id().
|
RequestID :: id().
|
||||||
@ -557,7 +579,7 @@ sysops(Realm) ->
|
|||||||
%% Private function to wrap the necessary bits up.
|
%% Private function to wrap the necessary bits up.
|
||||||
|
|
||||||
request(Action) ->
|
request(Action) ->
|
||||||
gen_server:call(?MODULE, {request, self(), Action}).
|
gen_server:call(?MODULE, {request, Action}).
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
@ -689,12 +711,19 @@ stop() ->
|
|||||||
handle_call({request, list}, _, State = #s{cx = CX}) ->
|
handle_call({request, list}, _, State = #s{cx = CX}) ->
|
||||||
Realms = cx_realms(CX),
|
Realms = cx_realms(CX),
|
||||||
{reply, {ok, Realms}, State};
|
{reply, {ok, Realms}, State};
|
||||||
handle_call({request, Requestor, Action}, From, State = #s{id = ID}) ->
|
handle_call({request, Action}, From, State = #s{id = ID}) ->
|
||||||
NewID = ID + 1,
|
NewID = ID + 1,
|
||||||
_ = gen_server:reply(From, {ok, NewID}),
|
_ = gen_server:reply(From, {ok, NewID}),
|
||||||
|
Requestor = element(1, From),
|
||||||
NextState = do_request(Requestor, Action, State#s{id = NewID}),
|
NextState = do_request(Requestor, Action, State#s{id = NewID}),
|
||||||
NewState = eval_queue(NextState),
|
NewState = eval_queue(NextState),
|
||||||
{noreply, NewState};
|
{noreply, NewState};
|
||||||
|
handle_call({install, PackageID}, _, State) ->
|
||||||
|
{Result, NewState} = do_install(PackageID, State),
|
||||||
|
{reply, Result, NewState};
|
||||||
|
handle_call({import_zsp, Path}, _, State) ->
|
||||||
|
Result = do_import_zsp(Path),
|
||||||
|
{reply, Result, NewState};
|
||||||
handle_call(Unexpected, From, State) ->
|
handle_call(Unexpected, From, State) ->
|
||||||
ok = log(warning, "Unexpected call ~tp: ~tp", [From, Unexpected]),
|
ok = log(warning, "Unexpected call ~tp: ~tp", [From, Unexpected]),
|
||||||
{noreply, State}.
|
{noreply, State}.
|
||||||
@ -1208,6 +1237,86 @@ drop_requests(ReqIDs, Dropped, Requests) ->
|
|||||||
lists:fold(Partition, {Dropped, Requests}, ReqIDs).
|
lists:fold(Partition, {Dropped, Requests}, ReqIDs).
|
||||||
|
|
||||||
|
|
||||||
|
-spec install(PackageID, State) -> {Result, NewState}
|
||||||
|
when PackageID :: zx:package_id(),
|
||||||
|
State :: state(),
|
||||||
|
Result :: zx:outcome(),
|
||||||
|
NewState :: state().
|
||||||
|
%% @private
|
||||||
|
%% FIXME: This is about as useful as psuedocode at the moment. Meh.
|
||||||
|
|
||||||
|
install(PackageID, State) ->
|
||||||
|
Path = zx_lib:zxp_path(PackageID),
|
||||||
|
case file:is_regular(Path) of
|
||||||
|
true ->
|
||||||
|
do_install(PackageID, Path);
|
||||||
|
false ->
|
||||||
|
ok = do_fetch(PackageID),
|
||||||
|
do_install(PackageID, Path)
|
||||||
|
end.
|
||||||
|
|
||||||
|
|
||||||
|
-spec do_import_zsp(file:filename()) -> zx:outcome().
|
||||||
|
%% @private
|
||||||
|
%% Dealing with data from the (probably local) filesystem can fail in a bajillion ways
|
||||||
|
%% and spring memory leaks if one tries to get too clever. So I'm sidestepping all the
|
||||||
|
%% madness with a "try++" here: spawning a suicidal helper.
|
||||||
|
|
||||||
|
do_import_zsp(Path) ->
|
||||||
|
{Pid, Mon} = spawn_monitor(fun() -> actually_import(Path) end),
|
||||||
|
receive
|
||||||
|
{Pid, Outcome} ->
|
||||||
|
true = demonitor(Mon, [flush]),
|
||||||
|
Outcome;
|
||||||
|
{'DOWN', Pid, process, Mon, Info} ->
|
||||||
|
{error, Info};
|
||||||
|
after 5000 ->
|
||||||
|
{error, timeout}
|
||||||
|
end.
|
||||||
|
|
||||||
|
|
||||||
|
-spec actually_import(ZspPath) -> no_return()
|
||||||
|
when ZspPath :: file:filename().
|
||||||
|
%% @private
|
||||||
|
%% The happy path of .zsp installation.
|
||||||
|
%% Must NEVER be executed by the zx_daemon directly.
|
||||||
|
|
||||||
|
%% More generally, there are a few phases:
|
||||||
|
%% 1- Loading the binary to extract the PackageID
|
||||||
|
%% 2- Checking the signature
|
||||||
|
%% 3- Moving the file to the cache
|
||||||
|
%% 4- Wiping the destination directory
|
||||||
|
%% 5- Extracting the TarGz to the destination
|
||||||
|
%% Some combination of functions should make these steps happen in a way that isn't
|
||||||
|
%% totally ridiculous, OR the bullet should just be bitten an allow for the
|
||||||
|
%% redundant lines here and there in different package management functions.
|
||||||
|
%%
|
||||||
|
%% Use cases are:
|
||||||
|
%% - Install a missing package from upstream
|
||||||
|
%% - Install a missing package from the local cache
|
||||||
|
%% - Reinstall a package from the local cache
|
||||||
|
%% - Import a package to the cache from the local filesystem and install it
|
||||||
|
%%
|
||||||
|
%% The Correct Approach as determine by The Royal Me is that I'm going to accept the
|
||||||
|
%% redundant code in the short-term because the data format is already decided.
|
||||||
|
%% If a place to get more fancy with the phases becomes really obvious after writing
|
||||||
|
%% identicalish segements of functions a few places then I'll break things apart.
|
||||||
|
|
||||||
|
actually_import(ZspPath) ->
|
||||||
|
{ok, Bin = <<Size:24, Sig:Size/binary, Signed/binary>>} = file:read_file(ZspPath),
|
||||||
|
<<MetaSize:16, MetaBin:MetaSize/binary, TarGz/binary>> = Signed,
|
||||||
|
{ok, {PackageID, SigKeyName}} = zx_lib:b_to_ts(MetaBin),
|
||||||
|
{ok, PubKey} = zx_key:load(public, {element(1, PackageID), SigKeyName}),
|
||||||
|
true = zx_key:verify(Signed, Sig, PubKey),
|
||||||
|
ok = file:write_file(zx_lib:zsp_path(PackageID), Bin),
|
||||||
|
Destination = zx_lib:ppath(lib, PackageID),
|
||||||
|
ok = filelib:ensure_dir(Destination),
|
||||||
|
ok = zx_lib:rm_rf(Destination),
|
||||||
|
ok = file:make_dir(Destination),
|
||||||
|
ok = erl_tar:extract(TarGZ, [{cwd, Destination}]),
|
||||||
|
zx_daemon ! {self(), ok}.
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
%%% Monitor Index ADT Interface Functions
|
%%% Monitor Index ADT Interface Functions
|
||||||
|
|
||||||
|
|||||||
@ -21,7 +21,7 @@
|
|||||||
|
|
||||||
%%% Functions
|
%%% Functions
|
||||||
|
|
||||||
-spec ensure_keypair(zx:key_id()) -> true | no_return().
|
-spec ensure_keypair(zx:key_id()) -> zx:outcome().
|
||||||
%% @private
|
%% @private
|
||||||
%% Check if both the public and private key based on KeyID exists.
|
%% Check if both the public and private key based on KeyID exists.
|
||||||
|
|
||||||
@ -30,17 +30,17 @@ ensure_keypair(KeyID = {Realm, KeyName}) ->
|
|||||||
{true, true} ->
|
{true, true} ->
|
||||||
true;
|
true;
|
||||||
{false, true} ->
|
{false, true} ->
|
||||||
Message = "Public key ~tp/~tp cannot be found",
|
Format = "Public key ~tp/~tp cannot be found",
|
||||||
ok = log(error, Message, [Realm, KeyName]),
|
Message = io_lib:format(Message, [Realm, KeyName]),
|
||||||
halt(1);
|
{error, Message, 2};
|
||||||
{true, false} ->
|
{true, false} ->
|
||||||
Message = "Private key ~tp/~tp cannot be found",
|
Format = "Private key ~tp/~tp cannot be found",
|
||||||
ok = log(error, Message, [Realm, KeyName]),
|
Message = io_lib:format(Message, [Realm, KeyName]),
|
||||||
halt(1);
|
{error, Message, 2};
|
||||||
{false, false} ->
|
{false, false} ->
|
||||||
Message = "Key pair ~tp/~tp cannot be found",
|
Format = "Key pair ~tp/~tp cannot be found",
|
||||||
ok = log(error, Message, [Realm, KeyName]),
|
Message = io_lib:format(Message, [Realm, KeyName]),
|
||||||
halt(1)
|
{error, Message, 2}
|
||||||
end.
|
end.
|
||||||
|
|
||||||
|
|
||||||
@ -120,39 +120,51 @@ generate_rsa(KeyID = {Realm, KeyName}) ->
|
|||||||
PubFile = path(public, KeyID),
|
PubFile = path(public, KeyID),
|
||||||
ok = lists:foreach(fun zx_lib:halt_if_exists/1, [PemFile, KeyFile, PubFile]),
|
ok = lists:foreach(fun zx_lib:halt_if_exists/1, [PemFile, KeyFile, PubFile]),
|
||||||
ok = log(info, "Generating ~p and ~p. Please be patient...", [KeyFile, PubFile]),
|
ok = log(info, "Generating ~p and ~p. Please be patient...", [KeyFile, PubFile]),
|
||||||
ok = gen_p_key(KeyFile),
|
case gen_p_key(KeyFile) of
|
||||||
ok = der_to_pem(KeyFile, PemFile),
|
ok ->
|
||||||
{ok, PemBin} = file:read_file(PemFile),
|
ok = der_to_pem(KeyFile, PemFile),
|
||||||
[PemData] = public_key:pem_decode(PemBin),
|
{ok, PemBin} = file:read_file(PemFile),
|
||||||
Pub = public_key:pem_entry_decode(PemData),
|
[PemData] = public_key:pem_decode(PemBin),
|
||||||
PubDer = public_key:der_encode('RSAPublicKey', Pub),
|
Pub = public_key:pem_entry_decode(PemData),
|
||||||
ok = file:write_file(PubFile, PubDer),
|
PubDer = public_key:der_encode('RSAPublicKey', Pub),
|
||||||
case check_key(KeyFile, PubFile) of
|
ok = file:write_file(PubFile, PubDer),
|
||||||
true ->
|
case check_key(KeyFile, PubFile) of
|
||||||
ok = file:delete(PemFile),
|
true ->
|
||||||
log(info, "~ts and ~ts agree", [KeyFile, PubFile]);
|
ok = file:delete(PemFile),
|
||||||
false ->
|
log(info, "~ts and ~ts agree", [KeyFile, PubFile]);
|
||||||
ok = lists:foreach(fun file:delete/1, [PemFile, KeyFile, PubFile]),
|
false ->
|
||||||
ok = log(error, "Something has gone wrong."),
|
ok = lists:foreach(fun file:delete/1, [PemFile, KeyFile, PubFile]),
|
||||||
|
ok = log(error, "Something has gone wrong."),
|
||||||
|
{error, keygen_fail}
|
||||||
|
end;
|
||||||
|
{error, no_ssl} ->
|
||||||
|
ok = log(error, "OpenSSL not found."),
|
||||||
{error, keygen_fail}
|
{error, keygen_fail}
|
||||||
end.
|
end.
|
||||||
|
|
||||||
|
|
||||||
-spec gen_p_key(KeyFile) -> ok
|
-spec gen_p_key(KeyFile) -> Result
|
||||||
when KeyFile :: file:filename().
|
when KeyFile :: file:filename()
|
||||||
|
Result :: ok
|
||||||
|
| {error, no_ssl}.
|
||||||
%% @private
|
%% @private
|
||||||
%% Format an openssl shell command that will generate proper 16k RSA keys.
|
%% Format an openssl shell command that will generate proper 16k RSA keys.
|
||||||
|
|
||||||
gen_p_key(KeyFile) ->
|
gen_p_key(KeyFile) ->
|
||||||
Command =
|
case openssl() of
|
||||||
io_lib:format("~ts genpkey"
|
{ok, OpenSSL} ->
|
||||||
" -algorithm rsa"
|
Command =
|
||||||
" -out ~ts"
|
io_lib:format("~ts genpkey"
|
||||||
" -outform DER"
|
" -algorithm rsa"
|
||||||
" -pkeyopt rsa_keygen_bits:16384",
|
" -out ~ts"
|
||||||
[openssl(), KeyFile]),
|
" -outform DER"
|
||||||
Out = os:cmd(Command),
|
" -pkeyopt rsa_keygen_bits:16384",
|
||||||
io:format(Out).
|
[OpenSSL, KeyFile]),
|
||||||
|
Out = os:cmd(Command),
|
||||||
|
io:format(Out);
|
||||||
|
Error ->
|
||||||
|
Error
|
||||||
|
end.
|
||||||
|
|
||||||
|
|
||||||
-spec der_to_pem(KeyFile, PemFile) -> ok
|
-spec der_to_pem(KeyFile, PemFile) -> ok
|
||||||
@ -196,11 +208,14 @@ check_key(KeyFile, PubFile) ->
|
|||||||
public_key:verify(TestMessage, sha512, Signature, Pub).
|
public_key:verify(TestMessage, sha512, Signature, Pub).
|
||||||
|
|
||||||
|
|
||||||
-spec openssl() -> Executable | no_return()
|
-spec openssl() -> Result
|
||||||
when Executable :: file:filename().
|
when Result :: {ok, Executable}
|
||||||
|
| {error, no_ssl},
|
||||||
|
Executable :: file:filename().
|
||||||
%% @private
|
%% @private
|
||||||
%% Attempt to locate the installed openssl executable for use in shell commands.
|
%% Attempt to locate the installed openssl executable for use in shell commands.
|
||||||
%% Halts execution with an error message if the executable cannot be found.
|
%% TODO: Determine whether it is even worth it to perform this check VS restricting
|
||||||
|
%% os:cmd/1 directed zx_key functions by platform.
|
||||||
|
|
||||||
openssl() ->
|
openssl() ->
|
||||||
OpenSSL =
|
OpenSSL =
|
||||||
@ -208,16 +223,15 @@ openssl() ->
|
|||||||
{unix, _} -> "openssl";
|
{unix, _} -> "openssl";
|
||||||
{win32, _} -> "openssl.exe"
|
{win32, _} -> "openssl.exe"
|
||||||
end,
|
end,
|
||||||
ok =
|
case os:find_executable(OpenSSL) of
|
||||||
case os:find_executable(OpenSSL) of
|
false ->
|
||||||
false ->
|
ok = log(error, "OpenSSL could not be found in this system's PATH."),
|
||||||
ok = log(error, "OpenSSL could not be found in this system's PATH."),
|
ok = log(error, "Install OpenSSL and then retry."),
|
||||||
ok = log(error, "Install OpenSSL and then retry."),
|
{error, no_ssl};
|
||||||
error_exit("Missing system dependenct: OpenSSL", ?LINE);
|
Path ->
|
||||||
Path ->
|
log(info, "OpenSSL executable found at: ~ts", [Path]),
|
||||||
log(info, "OpenSSL executable found at: ~ts", [Path])
|
OpenSSL
|
||||||
end,
|
end.
|
||||||
OpenSSL.
|
|
||||||
|
|
||||||
|
|
||||||
-spec load(Type, KeyID) -> Result
|
-spec load(Type, KeyID) -> Result
|
||||||
@ -242,20 +256,16 @@ load(Type, KeyID) ->
|
|||||||
end.
|
end.
|
||||||
|
|
||||||
|
|
||||||
-spec verify(Data, Signature, PubKey) -> ok | no_return()
|
-spec verify(Data, Signature, PubKey) -> boolean()
|
||||||
when Data :: binary(),
|
when Data :: binary(),
|
||||||
Signature :: binary(),
|
Signature :: binary(),
|
||||||
PubKey :: public_key:rsa_public_key().
|
PubKey :: public_key:rsa_public_key().
|
||||||
%% @private
|
%% @private
|
||||||
%% Verify the RSA Signature of some Data against the given PubKey or halt execution.
|
%% Curry out the choice of algorithm. This will probably disappear in a few more
|
||||||
%% This function always assumes sha512 is the algorithm being used.
|
%% versions as the details of sha512 and RSA gradually give way to the Brave New World.
|
||||||
%% Should only ever be called by the initial launch process.
|
|
||||||
|
|
||||||
verify(Data, Signature, PubKey) ->
|
verify(Data, Signature, PubKey) ->
|
||||||
case public_key:verify(Data, sha512, Signature, PubKey) of
|
public_key:verify(Data, sha512, Signature, PubKey).
|
||||||
true -> ok;
|
|
||||||
false -> error_exit("Bad package signature!", ?LINE)
|
|
||||||
end.
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@ -25,11 +25,9 @@
|
|||||||
valid_lower0_9/1, valid_label/1, valid_version/1,
|
valid_lower0_9/1, valid_label/1, valid_version/1,
|
||||||
string_to_version/1, version_to_string/1,
|
string_to_version/1, version_to_string/1,
|
||||||
package_id/1, package_string/1,
|
package_id/1, package_string/1,
|
||||||
namify_zsp/1, namify_tgz/1,
|
namify_zsp/1, zsp_path/1,
|
||||||
zsp_path/1,
|
|
||||||
find_latest_compatible/2, installed/1,
|
find_latest_compatible/2, installed/1,
|
||||||
realm_conf/1, load_realm_conf/1,
|
realm_conf/1, load_realm_conf/1,
|
||||||
extract_zsp_or_die/1, halt_if_exists/1,
|
|
||||||
build/0,
|
build/0,
|
||||||
rm_rf/1, rm/1,
|
rm_rf/1, rm/1,
|
||||||
b_to_t/1, b_to_ts/1]).
|
b_to_t/1, b_to_ts/1]).
|
||||||
@ -582,37 +580,17 @@ package_string(_) ->
|
|||||||
when PackageID :: zx:package_id(),
|
when PackageID :: zx:package_id(),
|
||||||
ZrpFileName :: file:filename().
|
ZrpFileName :: file:filename().
|
||||||
%% @private
|
%% @private
|
||||||
%% Map an PackageID to its correct .zsp package file name.
|
%% Map a PackageID to its correct .zsp package file name.
|
||||||
|
|
||||||
namify_zsp(PackageID) -> namify(PackageID, "zsp").
|
namify_zsp(PackageID) ->
|
||||||
|
|
||||||
|
|
||||||
-spec namify_tgz(PackageID) -> TgzFileName
|
|
||||||
when PackageID :: zx:package_id(),
|
|
||||||
TgzFileName :: file:filename().
|
|
||||||
%% @private
|
|
||||||
%% Map an PackageID to its correct gzipped tarball source bundle filename.
|
|
||||||
|
|
||||||
namify_tgz(PackageID) -> namify(PackageID, "tgz").
|
|
||||||
|
|
||||||
|
|
||||||
-spec namify(PackageID, Suffix) -> FileName
|
|
||||||
when PackageID :: zx:package_id(),
|
|
||||||
Suffix :: string(),
|
|
||||||
FileName :: file:filename().
|
|
||||||
%% @private
|
|
||||||
%% Converts an PackageID to a canonical string, then appends the provided
|
|
||||||
%% filename Suffix.
|
|
||||||
|
|
||||||
namify(PackageID, Suffix) ->
|
|
||||||
{ok, PackageString} = package_string(PackageID),
|
{ok, PackageString} = package_string(PackageID),
|
||||||
PackageString ++ "." ++ Suffix.
|
PackageString ++ ".zsp".
|
||||||
|
|
||||||
|
|
||||||
-spec zsp_path(zx:package_id()) -> file:filename().
|
-spec zsp_path(zx:package_id()) -> file:filename().
|
||||||
|
|
||||||
zsp_path(PackageID) ->
|
zsp_path({Realm, _, _}) ->
|
||||||
filename:join(path(zsp, element(1, PackageID)), namify_zsp(PackageID)).
|
filename:join(path(zsp, Realm), namify_zsp(PackageID)).
|
||||||
|
|
||||||
|
|
||||||
-spec find_latest_compatible(Version, Versions) -> Result
|
-spec find_latest_compatible(Version, Versions) -> Result
|
||||||
@ -683,7 +661,7 @@ realm_conf(Realm) ->
|
|||||||
| file:posix()
|
| file:posix()
|
||||||
| {Line :: integer(), Mod :: module(), Cause :: term()}.
|
| {Line :: integer(), Mod :: module(), Cause :: term()}.
|
||||||
%% @private
|
%% @private
|
||||||
%% Load the config for the given realm or halt with an error.
|
%% Load the config for the given realm.
|
||||||
|
|
||||||
load_realm_conf(Realm) ->
|
load_realm_conf(Realm) ->
|
||||||
Path = realm_conf(Realm),
|
Path = realm_conf(Realm),
|
||||||
@ -696,41 +674,6 @@ load_realm_conf(Realm) ->
|
|||||||
end.
|
end.
|
||||||
|
|
||||||
|
|
||||||
-spec extract_zsp_or_die(FileName) -> Files | no_return()
|
|
||||||
when FileName :: file:filename(),
|
|
||||||
Files :: [{file:filename(), binary()}].
|
|
||||||
%% @private
|
|
||||||
%% Extract a zsp archive, if possible. If not possible, halt execution with as accurate
|
|
||||||
%% an error message as can be managed.
|
|
||||||
|
|
||||||
extract_zsp_or_die(FileName) ->
|
|
||||||
case erl_tar:extract(FileName, [memory]) of
|
|
||||||
{ok, Files} ->
|
|
||||||
Files;
|
|
||||||
{error, {FileName, enoent}} ->
|
|
||||||
Message = "Can't find file ~ts.",
|
|
||||||
error_exit(Message, [FileName], ?LINE);
|
|
||||||
{error, invalid_tar_checksum} ->
|
|
||||||
Message = "~ts is not a valid zsp archive.",
|
|
||||||
error_exit(Message, [FileName], ?LINE);
|
|
||||||
{error, Reason} ->
|
|
||||||
Message = "Extracting package file failed with: ~160tp.",
|
|
||||||
error_exit(Message, [Reason], ?LINE)
|
|
||||||
end.
|
|
||||||
|
|
||||||
|
|
||||||
-spec halt_if_exists(file:filename()) -> ok | no_return().
|
|
||||||
%% @private
|
|
||||||
%% A helper function to guard against overwriting an existing file. Halts execution if
|
|
||||||
%% the file is found to exist.
|
|
||||||
|
|
||||||
halt_if_exists(Path) ->
|
|
||||||
case filelib:is_file(Path) of
|
|
||||||
true -> error_exit("~ts already exists! Halting.", [Path], ?LINE);
|
|
||||||
false -> ok
|
|
||||||
end.
|
|
||||||
|
|
||||||
|
|
||||||
-spec build() -> ok.
|
-spec build() -> ok.
|
||||||
%% @private
|
%% @private
|
||||||
%% Run any local `zxmake' script needed by the project for non-Erlang code (if present),
|
%% Run any local `zxmake' script needed by the project for non-Erlang code (if present),
|
||||||
@ -755,6 +698,7 @@ build() ->
|
|||||||
-spec rm_rf(file:filename()) -> ok | {error, file:posix()}.
|
-spec rm_rf(file:filename()) -> ok | {error, file:posix()}.
|
||||||
%% @private
|
%% @private
|
||||||
%% Recursively remove files and directories. Equivalent to `rm -rf'.
|
%% Recursively remove files and directories. Equivalent to `rm -rf'.
|
||||||
|
%% Does not return an error on a nonexistant path.
|
||||||
|
|
||||||
rm_rf(Path) ->
|
rm_rf(Path) ->
|
||||||
case filelib:is_dir(Path) of
|
case filelib:is_dir(Path) of
|
||||||
@ -764,7 +708,10 @@ rm_rf(Path) ->
|
|||||||
ok = lists:foreach(fun rm/1, Contents),
|
ok = lists:foreach(fun rm/1, Contents),
|
||||||
file:del_dir(Path);
|
file:del_dir(Path);
|
||||||
false ->
|
false ->
|
||||||
file:delete(Path)
|
case filelib:is_regular(Path) of
|
||||||
|
true -> file:delete(Path);
|
||||||
|
false -> ok
|
||||||
|
end
|
||||||
end.
|
end.
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@ -10,7 +10,7 @@
|
|||||||
-copyright("Craig Everett <zxq9@zxq9.com>").
|
-copyright("Craig Everett <zxq9@zxq9.com>").
|
||||||
-license("GPL-3.0").
|
-license("GPL-3.0").
|
||||||
|
|
||||||
-export([initialize/2, assimilate/1, set_version/1,
|
-export([initialize/2, set_version/1,
|
||||||
list_realms/0, list_packages/1, list_versions/1,
|
list_realms/0, list_packages/1, list_versions/1,
|
||||||
set_dep/1, list_deps/0, list_deps/1, drop_dep/1, verup/1, package/1,
|
set_dep/1, list_deps/0, list_deps/1, drop_dep/1, verup/1, package/1,
|
||||||
add_realm/1, drop_realm/1,
|
add_realm/1, drop_realm/1,
|
||||||
@ -268,39 +268,6 @@ initialize_app_file({_, Name, Version}, AppStart) ->
|
|||||||
zx_lib:write_terms(AppFile, [AppProfile]).
|
zx_lib:write_terms(AppFile, [AppProfile]).
|
||||||
|
|
||||||
|
|
||||||
-spec assimilate(PackageFile) -> zx:outcome()
|
|
||||||
when PackageFile :: file:filename().
|
|
||||||
%% @private
|
|
||||||
%% Receives a path to a file containing package data, examines it, and copies it to a
|
|
||||||
%% canonical location under a canonical name.
|
|
||||||
|
|
||||||
assimilate(PackageFile) ->
|
|
||||||
Files = zx_lib:extract_zsp_or_die(PackageFile),
|
|
||||||
{ok, CWD} = file:get_cwd(),
|
|
||||||
ok = file:set_cwd(zx_lib:zomp_dir()),
|
|
||||||
{"zomp.meta", MetaBin} = lists:keyfind("zomp.meta", 1, Files),
|
|
||||||
Meta = binary_to_term(MetaBin),
|
|
||||||
PackageID = maps:get(package_id, Meta),
|
|
||||||
TgzFile = zx_lib:namify_tgz(PackageID),
|
|
||||||
{TgzFile, TgzData} = lists:keyfind(TgzFile, 1, Files),
|
|
||||||
{KeyID, Signature} = maps:get(sig, Meta),
|
|
||||||
{ok, PubKey} = zx_key:load(public, KeyID),
|
|
||||||
case public_key:verify(TgzData, sha512, Signature, PubKey) of
|
|
||||||
true ->
|
|
||||||
ok = file:copy(PackageFile, zx_lib:zsp_path(PackageID)),
|
|
||||||
assimilate2(CWD, PackageID);
|
|
||||||
false ->
|
|
||||||
{error, "Bad package signature.", 1}
|
|
||||||
end.
|
|
||||||
|
|
||||||
|
|
||||||
assimilate2(CWD, PackageID) ->
|
|
||||||
ok = file:set_cwd(CWD),
|
|
||||||
Message = "~ts is now locally available.",
|
|
||||||
{ok, PackageString} = zx_lib:package_string(PackageID),
|
|
||||||
log(info, Message, [PackageString]).
|
|
||||||
|
|
||||||
|
|
||||||
-spec set_version(VersionString) -> zx:outcome()
|
-spec set_version(VersionString) -> zx:outcome()
|
||||||
when VersionString :: string().
|
when VersionString :: string().
|
||||||
%% @private
|
%% @private
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user