Arrrrrganazing...

This commit is contained in:
Craig Everett 2019-12-18 17:09:31 +09:00
parent 1fde98932c
commit da456ef4e4
7 changed files with 385 additions and 454 deletions

View File

@ -39,7 +39,7 @@
-export_type([serial/0, package_id/0, package/0, realm/0, name/0, version/0,
identifier/0,
host/0,
key_data/0, key_bin/0, key_id/0, key_name/0,
key/0, key_data/0, key_bin/0, key_id/0, key_name/0,
user_id/0, user_name/0, contact_info/0, user_data/0,
lower0_9/0, label/0,
ss_tag/0, search_tag/0, description/0, package_type/0,
@ -60,11 +60,12 @@
Minor :: non_neg_integer() | z,
Patch :: non_neg_integer() | z}.
-type host() :: {string() | inet:ip_address(), inet:port_number()}.
-type key() :: term(). % Srsly. This is what public_key:der_decode/2 returns.
-type key_data() :: {Name :: key_name(),
Public :: none | key_bin(),
Private :: none | key_bin()}.
-type key_bin() :: {Sig :: none | {key_name(), binary()},
Der :: binary()}.
DER :: binary()}.
-type key_id() :: {realm(), key_name()}.
-type key_name() :: key_hash().
-type key_hash() :: binary().
@ -225,12 +226,12 @@ do(["sync", "keys"]) ->
done(zx_auth:sync_keys());
do(["create", "user"]) ->
done(zx_local:create_user());
do(["create", "userfile"]) ->
done(zx_local:create_userfile());
do(["create", "keypair"]) ->
done(zx_local:grow_a_pair());
do(["export", "user"]) ->
done(zx_local:export_user());
done(zx_local:export_user(zpuf));
do(["export", "user", "dangerous"]) ->
done(zx_local:export_user(zduf));
do(["import", "user", ZdufFile]) ->
done(zx_local:import_user(ZdufFile));
do(["list", "users", Realm]) ->
@ -888,9 +889,8 @@ usage_dev() ->
" zx reject PackageID~n"
" zx sync keys~n"
" zx create user~n"
" zx create userfile~n"
" zx create keypair~n"
" zx export user~n"
" zx export user [dangerous]~n"
" zx import user ZDUF~n"
" zx list users Realm~n"
" zx list packagers PackageName~n"

View File

@ -307,6 +307,7 @@ add_user(ZPUF) ->
add_user2(Bin) ->
case zx_lib:b_to_t(Bin) of
{ok, {UserInfo, KeyData}} -> add_user3(UserInfo, KeyData);
{ok, _} -> {error, "Malformed user file.", 1};
error -> {error, "Bad user file.", 1}
end.

View File

@ -152,8 +152,8 @@
list_sysops/1,
fetch/1, install/1, build/1,
wait_result/1, wait_results/1]).
-export([register_key/2, get_key/2, keybin/2,
find_keypair/1, have_key/2, list_keys/2]).
-export([register_key/2, get_key/2, get_keybin/2,
have_key/2, list_keys/2]).
-export([report/1, result/2, notify/2]).
-export([connect/0, disconnect/0]).
-export([conf/1, conf/2, hosts/0,
@ -187,8 +187,7 @@
dropped = maps:new() :: requests(),
timer = none :: none | reference(),
mx = mx_new() :: monitor_index(),
cx = new_cx() :: conn_index(),
kx = load_kx() :: key_index()}).
cx = new_cx() :: conn_index()}).
-record(conf,
{realms = zx_lib:list_realms() :: [zx:realm()],
@ -213,7 +212,7 @@
-record(rmeta,
{serial = 0 :: non_neg_integer(),
prime = {"zomp.tsuriai.jp", 11311} :: zx:host(),
key = [] :: zx:key_name(),
key = none :: none | zx:key_name(),
sysop = none :: zx:user_name(),
assigned = none :: none | managed | pid(),
available = [] :: [pid()]}).
@ -224,12 +223,6 @@
requests = [] :: [id()],
subs = [] :: [{pid(), zx:package()}]}).
-record(key,
{pubhash = none :: none | binary(),
pub = none :: none | public_key:public_key(),
keyhash = none :: none | binary(),
key = none :: none | public_key:private_key()}).
%% State Types
-type state() :: #s{}.
@ -239,11 +232,6 @@
| {unsubscribe, pid(), zx:package()}
| {request, pid(), id(), action()}.
-type requests() :: #{id() := {pid(), action()}}.
-type key_index() :: {rk_index(), pair_index(), key_owners()}.
-type rk_index() :: #{zx:realm() := key_registry()}.
-type pair_index() :: #{zx:key_hash() := zx:key_id()}.
-type key_registry() :: #{zx:key_hash() := #key{}}.
-type key_owners() :: #{zx:user_id() := {[zx:key_hash()], [zx:key_hash()]}}.
-type monitor_index() :: #{pid() := {reference(), category()}}.
-type conn_index() :: #cx{} | zomp | proxy.
-type realm_meta() :: #rmeta{}.
@ -606,8 +594,8 @@ drop_mirror(Host) ->
when Owner :: zx:realm() | zx:user_id(),
KeyData :: zx:key_data().
register_key(Owner, Data) ->
gen_server:call(?MODULE, {register_key, Owner, Data}).
register_key(Owner, KeyData) ->
gen_server:call(?MODULE, {register_key, Owner, KeyData}).
-spec get_key(Type, KeyID) -> Result
@ -624,23 +612,25 @@ get_key(Type, KeyID) ->
gen_server:call(?MODULE, {get_key, Type, KeyID}).
-spec keybin(Type, KeyID) -> Result
-spec get_keybin(Type, KeyID) -> Result
when Type :: public | private,
KeyID :: zx:key_id(),
Result :: {ok, binary()}
| {error, file:posix()}.
keybin(Type, KeyID) ->
gen_server:call(?MODULE, {keybin, Type, KeyID}).
get_keybin(Type, KeyID) ->
gen_server:call(?MODULE, {get_keybin, Type, KeyID}).
-spec find_keypair(KeyName) -> Result
when KeyName :: zx:key_name(),
Result :: {ok, zx:key_id()}
| error.
find_keypair(KeyName) ->
gen_server:call(?MODULE, {find_keypair, KeyName}).
% TODO: This should be an external request to a Zomp node.
% FIXME: Determine how this should work.
%-spec find_keypair(KeyName) -> Result
% when KeyName :: zx:key_name(),
% Result :: {ok, zx:key_id()}
% | error.
%
%find_keypair(KeyName) ->
% gen_server:call(?MODULE, {find_keypair, KeyName}).
-spec have_key(Type, KeyID) -> boolean()
@ -814,29 +804,25 @@ handle_call({build, PackageID}, _, State) ->
Result = do_build(PackageID),
NewState = eval_queue(State),
{reply, Result, NewState};
handle_call({get_key, Type, KeyID}, _, State = #s{kx = KX}) ->
Result = do_get_key(Type, KeyID, element(1, KX)),
handle_call({get_key, Type, KeyID}, _, State) ->
Result = do_get_key(Type, KeyID),
NewState = eval_queue(State),
{reply, Result, NewState};
handle_call({keybin, Type, KeyID}, _, State = #s{kx = KX}) ->
Result = do_keybin(Type, KeyID, element(1, KX)),
handle_call({get_keybin, Type, KeyID}, _, State) ->
Result = do_get_keybin(Type, KeyID),
NewState = eval_queue(State),
{reply, Result, NewState};
handle_call({find_keypair, KeyName}, _, State = #s{kx = KX}) ->
Result = maps:find(KeyName, element(2, KX)),
handle_call({have_key, Type, KeyID}, _, State) ->
Result = do_have_key(Type, KeyID),
NewState = eval_queue(State),
{reply, Result, NewState};
handle_call({have_key, Type, KeyID}, _, State = #s{kx = KX}) ->
Result = do_have_key(Type, KeyID, element(1, KX)),
NewState = eval_queue(State),
{reply, Result, NewState};
handle_call({list_keys, Type, Owner}, _, State = #s{kx = KX}) ->
Result = do_list_keys(Type, Owner, KX),
handle_call({list_keys, Type, Owner}, _, State) ->
Result = do_list_keys(Type, Owner),
NewState = eval_queue(State),
{reply, Result, NewState};
handle_call({register_key, Owner, Data}, _, State) ->
{Result, NextState} = do_register_key(Owner, Data, State),
NewState = eval_queue(NextState),
Result = do_register_key(Owner, Data),
NewState = eval_queue(State),
{reply, Result, NewState};
handle_call({takeover, Realm}, _, State = #s{conf = Conf}) ->
{Result, NewConf} = do_takeover(Realm, Conf),
@ -1868,11 +1854,9 @@ do_takeover(Realm, C = #conf{realms = Realms, managed = Managed}) ->
end.
-spec do_abdicate(Realm, State) -> {Result, NewState}
-spec do_abdicate(Realm, State) -> NewState
when Realm :: zx:realm(),
State :: state(),
Result :: ok
| {error, unmanaged},
NewState :: state().
do_abdicate(Realm, State = #s{conf = C = #conf{managed = Managed}}) ->
@ -1882,271 +1866,121 @@ do_abdicate(Realm, State = #s{conf = C = #conf{managed = Managed}}) ->
NewC = C#conf{managed = NewManaged},
ok = save_conf(NewC),
ok = log(info, "No longer managing realm: ~160tp", [Realm]),
{ok, State#s{conf = NewC}};
State#s{conf = NewC};
false ->
ok = tell(error, "Cannot abdicate an unmanaged realm."),
{{error, unmanaged}, State}
State
end.
%%% Key Functions
-spec load_kx() -> key_index().
load_kx() ->
case file:read_file(key_stash_path()) of
{ok, Bin} -> binary_to_term(Bin);
{error, enoent} -> {maps:new(), maps:new(), maps:new()}
end.
key_stash_path() ->
filename:join(zx_lib:path(key), "key.stash").
-spec do_register_key(Owner, KeyData, State) -> {ok, NewState}
-spec do_register_key(Owner, KeyData) -> Result
when Owner :: zx:realm() | zx:user_id(),
KeyData :: zx:key_data(),
State :: state(),
NewState :: state().
Result :: ok
| {error, Reason},
Reason :: bad_user
| bad_realm
| file:posix().
do_register_key(UserID = {Realm, _}, Data, State = #s{kx = {RX, PX, UK}}) ->
{NewRX, NewPX} = do_register_realm_key(Realm, Data, RX, PX),
NewUK =
case Data of
{PairHash, none, {_, K}} when is_binary(K) ->
Update = fun({Ps, Ks}) -> {Ps, [PairHash | Ks]} end,
maps:update_with(UserID, Update, {[], [PairHash]}, UK);
{PairHash, {_, P}, none} when is_binary(P) ->
Update = fun({Ps, Ks}) -> {[PairHash | Ps], Ks} end,
maps:update_with(UserID, Update, {[PairHash], []}, UK);
{PairHash, {_, P}, {_, K}} when is_binary(P), is_binary(K) ->
Update = fun({Ps, Ks}) -> {[PairHash | Ps], [PairHash | Ks]} end,
maps:update_with(UserID, Update, {[PairHash], [PairHash]}, UK)
do_register_key(Owner = {Realm, UserName}, KeyData) ->
case zx_userconf:load(Owner) of
{ok, UC} ->
do_register_key2(Realm, UC, KeyData);
{error, bad_user} ->
UC = zx_userconf:new(),
NewUC = UC#{realm => Realm, username => UserName},
do_register_key2(Realm, NewUC, KeyData);
Error ->
Error
end.
do_register_key2(Realm, UC = #{keys := Keys}, KeyData = {KeyHash, _, _}) ->
NewUC =
case lists:member(KeyHash, Keys) of
false -> UC#{keys => [KeyHash | Keys]};
true -> UC
end,
NewKX = {NewRX, NewPX, NewUK},
ok = stash_keys(NewKX),
{ok, State#s{kx = NewKX}};
do_register_key(Realm, Data, State = #s{kx = {RX, PX, UK}}) ->
{NewRX, NewPX} = do_register_realm_key(Realm, Data, RX, PX),
NewKX = {NewRX, NewPX, UK},
ok = stash_keys(NewKX),
{ok, State#s{kx = NewKX}}.
ok = zx_userconf:save(NewUC),
do_register_key3(Realm, KeyData).
do_register_realm_key(Realm, Data, RealmIndex, PairIndex) ->
PairHash = element(1, Data),
{K, Pairs} = do_register_realm_key2(Realm, Data),
KeyRegistry = maps:get(Realm, RealmIndex, #{}),
NewKeyRegistry = update_registry(PairHash, K, KeyRegistry),
NewRealmIndex = maps:put(Realm, NewKeyRegistry, RealmIndex),
NewPairIndex = maps:merge(PairIndex, Pairs),
{NewRealmIndex, NewPairIndex}.
do_register_realm_key2(Realm, {PairHash, {_, PubBin}, none}) ->
PubHash = crypto:hash(sha512, PubBin),
ok = store_key(public, {Realm, PairHash}, PubBin),
Pub = public_key:der_decode('RSAPublicKey', PubBin),
K = #key{pubhash = PubHash, pub = Pub},
Pairs = #{PubHash => PairHash},
{K, Pairs};
do_register_realm_key2(Realm, {PairHash, none, {_, KeyBin}}) ->
KeyHash = crypto:hash(sha512, KeyBin),
ok = store_key(private, {Realm, PairHash}, KeyBin),
Key = public_key:der_decode('RSAPrivateKey', KeyBin),
K = #key{keyhash = KeyHash, key = Key},
Pairs = #{KeyHash => PairHash},
{K, Pairs};
do_register_realm_key2(Realm, {PairHash, {_, PubBin}, {_, KeyBin}}) ->
PubHash = crypto:hash(sha512, PubBin),
KeyHash = crypto:hash(sha512, KeyBin),
ok = store_key(public, {Realm, PairHash}, PubBin),
ok = store_key(private, {Realm, PairHash}, KeyBin),
PairHash = crypto:hash(sha512, <<PubHash/binary, KeyHash/binary>>),
Pub = public_key:der_decode('RSAPublicKey', PubBin),
Key = public_key:der_decode('RSAPrivateKey', KeyBin),
K = #key{pubhash = PubHash, pub = Pub, keyhash = KeyHash, key = Key},
Pairs = #{PubHash => PairHash, KeyHash => PairHash},
{K, Pairs}.
store_key(Type, KeyID, Bin) ->
Path = zx_key:path(Type, KeyID),
ok = filelib:ensure_dir(Path),
file:write_file(Path, Bin).
update_registry(PairHash,
K = #key{pubhash = none, keyhash = KeyHash, key = Key},
Registry) ->
case maps:find(PairHash, Registry) of
error ->
maps:put(PairHash, K, Registry);
{ok, #key{pubhash = none, keyhash = none}} ->
maps:put(PairHash, K, Registry);
{ok, OldK = #key{keyhash = none}} ->
maps:put(PairHash, OldK#key{keyhash = KeyHash, key = Key}, Registry);
{ok, #key{keyhash = KeyHash}} ->
Registry
end;
update_registry(PairHash,
K = #key{keyhash = none, pubhash = PubHash, pub = Pub},
Registry) ->
case maps:find(PairHash, Registry) of
error ->
maps:put(PairHash, K, Registry);
{ok, #key{pubhash = none, keyhash = none}} ->
maps:put(PairHash, K, Registry);
{ok, OldK = #key{pubhash = none}} ->
maps:put(PairHash, OldK#key{pubhash = PubHash, pub = Pub}, Registry);
{ok, #key{pubhash = PubHash}} ->
Registry
end;
update_registry(PairHash, K, Registry) ->
maps:put(PairHash, K, Registry).
stash_keys(KX) ->
Bin = term_to_binary(KX),
file:write_file(key_stash_path(), Bin).
do_register_key3(Realm, {KeyHash, none, {_, Key}}) ->
zx_key:save_bin(private, {Realm, KeyHash}, Key);
do_register_key3(Realm, {KeyHash, {_, Pub}, none}) ->
zx_key:save_bin(public, {Realm, KeyHash}, Pub);
do_register_key3(Realm, {KeyHash, {_, Pub}, {_, Key}}) ->
ok = zx_key:save_bin(public, {Realm, KeyHash}, Pub),
zx_key:save(private, {Realm, KeyHash}, Key);
do_register_key3(_, {_, none, none}) ->
ok.
-spec do_get_key(Type, KeyID, RK) -> Result
-spec do_get_key(Type, KeyID) -> Result
when Type :: public | private,
KeyID :: zx:key_id(),
RK :: rk_index(),
Result :: {ok, public_key:rsa_public_key() | public_key:rsa_private_key()}
| {error, Reason},
Reason :: bad_realm
| no_pub
| no_key
| bad_key.
| file:posix().
do_get_key(Type, {Realm, KeyName}, RK) ->
case maps:find(Realm, RK) of
{ok, Registry} -> do_get_key2(Type, KeyName, Registry);
error -> {error, bad_realm}
end.
do_get_key2(public, KeyName, Registry) ->
case maps:find(KeyName, Registry) of
{ok, #key{pub = none}} -> {error, no_pub};
{ok, #key{pub = Pub}} -> {ok, Pub};
error -> {error, bad_key}
end;
do_get_key2(private, KeyName, Registry) ->
case maps:find(KeyName, Registry) of
{ok, #key{key = none}} -> {error, no_key};
{ok, #key{key = Key}} -> {ok, Key};
error -> {error, bad_key}
end.
do_get_key(Type, KeyID) ->
zx_key:load(Type, KeyID).
-spec do_keybin(Type, KeyID, RK) -> Result
-spec do_get_keybin(Type, KeyID) -> Result
when Type :: public | private,
KeyID :: zx:key_id(),
RK :: rk_index(),
Result :: {ok, binary()}
| {error, bad_realm | bad_key | no_key | file:posix()}.
| {error, bad_realm | no_key | no_pub | file:posix()}.
do_keybin(Type, KeyID = {Realm, _}, RK) ->
case maps:find(Realm, RK) of
{ok, Registry} -> do_keybin2(Type, KeyID, Registry);
error -> {error, bad_realm}
end.
do_keybin2(public, KeyID = {_, KeyName}, Registry) ->
case maps:find(KeyName, Registry) of
{ok, #key{pubhash = none}} -> {error, no_key};
{ok, #key{}} -> file:read_file(zx_key:path(public, KeyID));
error -> {error, bad_key}
end;
do_keybin2(private, KeyID, Registry) ->
KeyName = element(2, KeyID),
case maps:find(KeyName, Registry) of
{ok, #key{keyhash = none}} -> {error, no_key};
{ok, #key{}} -> file:read_file(zx_key:path(private, KeyID));
error -> {error, bad_key}
end.
do_get_keybin(Type, KeyID) ->
zx_key:load_bin(Type, KeyID).
-spec do_have_key(Type, KeyID, RK) -> boolean()
-spec do_have_key(Type, KeyID) -> boolean()
when Type :: public | private,
KeyID :: zx:key_id(),
RK :: rk_index().
KeyID :: zx:key_id().
do_have_key(Type, {Realm, KeyName}, RK) ->
case maps:find(Realm, RK) of
{ok, Registry} ->
case maps:find(KeyName, Registry) of
{ok, K} -> do_have_key2(Type, K);
error -> false
end;
error ->
false
end.
do_have_key2(public, #key{pub = none}) -> false;
do_have_key2(private, #key{key = none}) -> false;
do_have_key2(_, #key{}) -> true.
do_have_key(Type, KeyID) ->
zx_key:exists(Type, KeyID).
-spec do_list_keys(Type, Owner, KX) -> Result
-spec do_list_keys(Type, Owner) -> Result
when Type :: public | private,
Owner :: zx:realm() | zx:user_id(),
KX :: key_index(),
Result :: {ok, [zx:key_hash()]}
| {error, bad_realm | bad_user}.
do_list_keys(public, UserID, {_, _, UK}) when is_tuple(UserID) ->
case maps:find(UserID, UK) of
{ok, {Keys, _}} -> {ok, Keys};
error -> {error, bad_user}
do_list_keys(Type, UserID = {Realm, _}) ->
case zx_userconf:load(UserID) of
{ok, #{keys := Keys}} -> do_list_keys2(Type, [{Realm, K} || K <- Keys]);
Error -> Error
end;
do_list_keys(private, UserID, {_, _, UK}) when is_tuple(UserID) ->
case maps:find(UserID, UK) of
{ok, {_, Keys}} -> {ok, Keys};
error -> {error, bad_user}
end;
do_list_keys(public, Realm, {RK, _, _}) ->
case maps:find(Realm, RK) of
{ok, Registry} ->
List = maps:to_list(Registry),
{ok, [ID || {ID, #key{pubhash = PH}} <- List, is_binary(PH)]};
error ->
{error, bad_realm}
end;
do_list_keys(private, Realm, {RK, _, _}) ->
case maps:find(Realm, RK) of
{ok, Registry} ->
List = maps:to_list(Registry),
{ok, [ID || {ID, #key{keyhash = KH}} <- List, is_binary(KH)]};
error ->
{error, bad_realm}
do_list_keys(Type, Realm) ->
case zx_lib:load_realm_conf(Realm) of
{ok, #{key := Key}} -> do_list_keys2(Type, [{Realm, Key}]);
Error -> Error
end.
do_list_keys2(Type, KeyIDs) ->
Exists = fun(ID) -> zx_key:exists(Type, ID) end,
{ok, lists:filter(Exists, KeyIDs)}.
-spec do_drop_realm(Realm, State) -> NewState
when Realm :: zx:realm(),
State :: state(),
NewState :: state().
do_drop_realm(Realm, State = #s{kx = {RealmIndex, Pairs, Owners}}) ->
NewKX =
case maps:take(Realm, RealmIndex) of
{KeyIndex, NextRealmIndex} ->
NewPairs = scrub_pairs(maps:values(KeyIndex), Pairs),
Screen = fun({{R, _}, _}) -> R == Realm end,
NewOwners = maps:filter(Screen, Owners),
{NextRealmIndex, NewPairs, NewOwners};
error ->
{RealmIndex, Pairs, Owners}
end,
do_drop_realm(Realm, State) ->
Dirs = [etc, var, tmp, log, key, zsp, lib],
RM = fun(D) -> ok = zx_lib:rm_rf(zx_lib:path(D, Realm)) end,
ok = lists:foreach(RM, Dirs),
{_, NewState} = do_abdicate(Realm, State),
NewState#s{kx = NewKX}.
scrub_pairs([#key{pubhash = PubHash, keyhash = KeyHash} | Rest], Pairs) ->
scrub_pairs(Rest, maps:without([PubHash, KeyHash], Pairs));
scrub_pairs([], Pairs) ->
Pairs.
do_abdicate(Realm, State).
-spec become_proxy(State) -> NewState

View File

@ -13,33 +13,15 @@
-copyright("Craig Everett <zxq9@zxq9.com>").
-license("GPL-3.0").
-export([path/2,
generate_rsa/1,
load/2, sign/2, verify/3]).
-export([generate_rsa/1,
save/3, load/2,
save_bin/3, load_bin/2,
exists/2,
sign/2, verify/3]).
-include("zx_logger.hrl").
%%% Functions
-spec path(public | private, zx:key_id()) -> file:filename().
path(Type, {Realm, KeyHash}) ->
Size = byte_size(KeyHash) * 8,
<<N:Size>> = KeyHash,
String = integer_to_list(N, 36),
Name =
case Type of
public -> String ++ ".pub.der";
private -> String ++ ".key.der"
end,
zx_lib:path(key, Realm, Name).
%%% Key generation
-spec generate_rsa(Owner) -> Result
when Owner :: zx:realm() | zx:user_id(),
Result :: {ok, zx:key_hash()}
@ -170,27 +152,93 @@ openssl() ->
end.
-spec save(Type, KeyID, Key) -> Result
when Type :: private | public,
KeyID :: zx:key_id(),
Key :: zx:key(),
Result :: ok
| {error, file:posix()}.
%% @private
%% Encode a key to DER and store it as a file.
save(Type, KeyID, Key) ->
KeyDER = public_key:der_encode(der_label(Type), Key),
save_bin(Type, KeyID, KeyDER).
-spec load(Type, KeyID) -> Result
when Type :: private | public,
KeyID :: zx:key_id(),
Result :: {ok, DecodedKey :: term()}
| {error, Reason :: file:posix()}.
Result :: {ok, DecodedKey :: zx:key()}
| {error, file:posix()}.
%% @private
%% Hide the details behind reading and loading DER encoded RSA key files.
%% Load and decode a DER encoded key from file.
load(Type, KeyID) ->
DerType =
case Type of
private -> 'RSAPrivateKey';
public -> 'RSAPublicKey'
end,
Path = path(Type, KeyID),
case file:read_file(Path) of
{ok, Bin} -> {ok, public_key:der_decode(DerType, Bin)};
Error -> Error
case load_bin(Type, KeyID) of
{ok, KeyDER} -> {ok, public_key:der_decode(der_label(Type), KeyDER)};
Error -> Error
end.
-spec save_bin(Type, KeyID, Bin) -> Result
when Type :: private | public,
KeyID :: zx:key_id(),
Bin :: binary(),
Result :: ok
| {error, bad_realm | file:posix()}.
%% @private
%% Save a key's binary representation directly.
save_bin(Type, KeyID = {Realm, _}, Bin) ->
case zx_lib:realm_exists(Realm) of
true -> save_bin2(Type, KeyID, Bin);
false -> {error, bad_realm}
end.
save_bin2(Type, KeyID, Bin) ->
Path = path(Type, KeyID),
ok = filelib:ensure_dir(Path),
ok = log(info, "Saving ~p key to ~p.", [Type, Path]),
file:write_file(Path, Bin).
-spec load_bin(Type, KeyID) -> Result
when Type :: private | public,
KeyID :: zx:key_id(),
Result :: {ok, binary()}
| {error, bad_realm | no_key | no_pub | file:posix()}.
%% @private
%% Load a binary key's representation directly without decoding it.
load_bin(Type, KeyID = {Realm, _}) ->
case zx_lib:realm_exists(Realm) of
true -> load_bin2(Type, KeyID);
false -> {error, bad_realm}
end.
load_bin2(Type, KeyID) ->
Path = path(Type, KeyID),
ok = log(info, "Loading ~p key from ~p.", [Type, Path]),
case file:read_file(Path) of
{ok, Bin} ->
{ok, Bin};
{error, enoent} ->
Reason = case Type of private -> no_key; public -> no_pub end,
{error, Reason};
Error ->
Error
end.
-spec exists(Type, KeyID) -> boolean()
when Type :: private | public,
KeyID :: zx:key_id().
exists(Type, KeyID) ->
filelib:is_regular(path(Type, KeyID)).
-spec sign(Data, Key) -> Signature
when Data :: binary(),
Key :: public_key:rsa_private_key(),
@ -210,3 +258,28 @@ sign(Data, Key) ->
verify(Data, Signature, PubKey) ->
public_key:verify(Data, sha512, Signature, PubKey).
der_label(private) -> 'RSAPrivateKey';
der_label(public) -> 'RSAPublicKey'.
-spec path(public | private, zx:key_id()) -> file:filename().
path(Type, {Realm, KeyHash}) ->
Name = name(Type, KeyHash),
zx_lib:path(key, Realm, Name).
string(KeyHash) ->
Size = byte_size(KeyHash) * 8,
<<N:Size>> = KeyHash,
integer_to_list(N, 36).
name(Type, KeyHash) ->
String = string(KeyHash),
case Type of
public -> String ++ ".pub.der";
private -> String ++ ".key.der"
end.

View File

@ -17,10 +17,10 @@
-export([zomp_dir/0, find_zomp_dir/0,
path/1, path/2, path/3, path/4, ppath/2,
new_logpath/1, userconf/1,
new_logpath/1,
force_dir/1, mktemp_dir/1, random_string/0,
list_realms/0, realm_exists/1,
get_prime/1, realm_meta/1,
get_prime/1,
read_project_meta/0, read_project_meta/1, read_package_meta/1,
write_project_meta/1, write_project_meta/2,
write_terms/2, exec_shell/1,
@ -141,12 +141,6 @@ new_logpath(PackageID = {Realm, Name, _}) ->
filename:join(Dir, FileName).
-spec userconf(zx:user_id()) -> file:filename().
userconf({Realm, UserName}) ->
filename:join(path(etc, Realm), UserName ++ ".user").
-spec force_dir(Path) -> Result
when Path :: file:filename(),
Result :: ok
@ -205,31 +199,12 @@ realm_exists(Realm) ->
%% Check the given Realm's config file for the current prime node and return it.
get_prime(Realm) ->
case realm_meta(Realm) of
{ok, RealmMeta} ->
{prime, Prime} = lists:keyfind(prime, 1, RealmMeta),
{ok, Prime};
Error ->
Error
case load_realm_conf(Realm) of
{ok, RealmMeta} -> maps:find(prime, RealmMeta);
Error -> Error
end.
-spec realm_meta(Realm) -> Result
when Realm :: string(),
Result :: {ok, Meta}
| {error, Reason},
Meta :: [{atom(), term()}],
Reason :: file:posix().
%% @private
%% Given a realm name, try to locate and read the realm's configuration file if it
%% exists, exiting with an appropriate error message if there is a problem reading
%% the file.
realm_meta(Realm) ->
RealmFile = filename:join(path(etc, Realm), "realm.conf"),
file:consult(RealmFile).
-spec read_project_meta() -> Result
when Result :: {ok, zx_zsp:meta()}
| {error, file:posix()}.
@ -666,7 +641,8 @@ realm_conf(Realm) ->
Result :: {ok, RealmConf}
| {error, Reason},
RealmConf :: map(),
Reason :: badarg
Reason :: bad_realm
| badarg
| terminated
| system_limit
| file:posix()
@ -679,6 +655,8 @@ load_realm_conf(Realm) ->
case file:consult(Path) of
{ok, C} ->
{ok, maps:from_list(C)};
{error, enoent} ->
{error, bad_realm};
Error ->
ok = log(warning, "Loading realm conf ~ts failed with: ~tw", [Path, Error]),
Error

View File

@ -14,7 +14,7 @@
-export([initialize/0, set_version/1,
list_realms/0, list_packages/1, list_versions/1,
latest/1, describe/1, provides/1,
list_keys/0, list_sysops/1,
list_sysops/1,
set_dep/1, list_deps/0, list_deps/1, drop_dep/1, verup/1, package/1,
update_meta/0, update_app_file/0,
import_realm/1, drop_realm/1, logpath/2,
@ -23,7 +23,7 @@
drop_mirror/0, drop_mirror/1, drop_mirror/2,
create_project/0,
grow_a_pair/0, grow_a_pair/2, drop_key/1,
create_user/0, create_userfile/0, export_user/0, import_user/1,
create_user/0, export_user/1, import_user/1,
create_realm/0, create_realmfile/0, create_realmfile/1]).
-export([create_user/1, select_private_key/1]).
@ -836,21 +836,6 @@ print_packages(PackageID) ->
io:format("~ts~n", [PackageString]).
-spec list_keys() -> zx:outcome().
list_keys() ->
{ok, Realm} = pick_realm(),
UserName = select_user(Realm),
{ok, ID} = zx_daemon:list_keys({Realm, UserName}),
case zx_daemon:wait_result(ID) of
{ok, RemoteKeys} ->
Print = fun(KN) -> io:format("~ts~n", [KN]) end,
lists:foreach(Print, RemoteKeys);
Error ->
Error
end.
-spec list_sysops(zx:realm()) -> zx:outcome().
list_sysops(Realm) ->
@ -1734,8 +1719,8 @@ package_exists({Realm, Package, _}) ->
grow_a_pair() ->
case pick_realm() of
{error, no_realms} -> {error, "No realms configured.", 61};
Realm -> grow_a_pair(Realm)
{ok, Realm} -> grow_a_pair(Realm);
{error, no_realms} -> {error, "No realms configured.", 61}
end.
grow_a_pair(Realm) ->
@ -1744,13 +1729,11 @@ grow_a_pair(Realm) ->
grow_a_pair(Realm, UserName) ->
case zx_key:generate_rsa(Realm) of
{ok, PairHash} ->
UserConf = zx_lib:userconf({Realm, UserName}),
{ok, UserData} = file:consult(UserConf),
Keys = proplists:get_value(keys, UserData),
NewKeys = [PairHash | Keys],
NewUserData = lists:keystor(keys, 1, {keys, NewKeys}, UserData),
zx_lib:write_terms(UserConf, NewUserData);
{ok, KeyHash} ->
UserID = {Realm, UserName},
{ok, UserConf = #{keys := Keys}} = zx_userconf:load(UserID),
NewUserConf = UserConf#{keys => [KeyHash | Keys]},
zx_userconf:save(NewUserConf);
Error ->
Error
end.
@ -1836,7 +1819,7 @@ store_realm(#realm_init{realm = Realm,
RealmConfPath = filename:join(zx_lib:path(etc, Realm), "realm.conf"),
ok = zx_lib:write_terms(RealmConfPath, RealmConf),
ok = create_realmfile(Realm),
ok = create_userfile(Realm, UserName),
ok = export_user(zpuf, Realm, UserName),
ZPUF = Realm ++ "-" ++ UserName ++ ".zpuf",
ZRF = Realm ++ ".zrf",
Message =
@ -2091,38 +2074,69 @@ store_user(#user_data{realm = Realm,
realname = RealName,
contact_info = ContactInfo,
keys = [KeyHash]}) ->
UserConf =
[{realm, Realm},
{username, UserName},
{realname, RealName},
{contact_info, ContactInfo},
{keys, [KeyHash]}],
Path = zx_lib:userconf({Realm, UserName}),
ok = filelib:ensure_dir(Path),
zx_lib:write_terms(Path, UserConf).
UC = zx_userconf:new(),
NewUC =
UC#{realm := Realm,
username := UserName,
realname := RealName,
contact_info := ContactInfo,
keys := [KeyHash]},
zx_userconf:save(NewUC).
-spec create_userfile() -> ok.
-spec export_user(Type) -> ok
when Type :: zpuf | zduf.
create_userfile() ->
export_user(Type) ->
case pick_realm() of
{error, no_realms} -> {error, "No realms configured.", 61};
Realm -> create_userfile(Realm)
{ok, Realm} -> export_user(Type, Realm);
{error, no_realms} -> {error, "No realms configured.", 61}
end.
create_userfile(Realm) ->
export_user(Type, Realm) ->
UserName = select_user(Realm),
create_userfile(Realm, UserName).
export_user(Type, Realm, UserName).
create_userfile(Realm, UserName) ->
UserConf = zx_lib:userconf({Realm, UserName}),
{ok, UserData} = file:consult(UserConf),
Keys = proplists:get_value(keys, UserData),
export_user(Type, Realm, UserName) ->
{ok, UserConf} = zx_userconf:load({Realm, UserName}),
Keys = maps:get(keys, UserConf),
{Load, Ext, Message} = exporter(Type, Realm),
KeyData = lists:foldl(Load, [], Keys),
UserFile = Realm ++ "-" ++ UserName ++ Ext,
UserData = maps:to_list(UserConf),
ok = tell(info, "UserData: ~p", [UserData]),
Bin = term_to_binary({UserData, KeyData}),
ok = file:write_file(UserFile, Bin),
io:format(Message, [UserFile, Realm]).
exporter(zduf, Realm) ->
Load =
fun(KeyName, Acc) ->
case zx_daemon:keybin(public, {Realm, KeyName}) of
Key =
case zx_daemon:get_keybin(private, {Realm, KeyName}) of
{ok, KDer} -> {none, KDer};
_ -> none
end,
Pub =
case zx_daemon:get_keybin(public, {Realm, KeyName}) of
{ok, PDer} -> {none, PDer};
_ -> none
end,
[{KeyName, Pub, Key} | Acc]
end,
Ext = ".zduf",
Message =
"Wrote Zomp DANGEROUS user file to ~ts.~n"
"WARNING: This file contains your PRIVATE KEYS and should NEVER be shared.~n"
"Its only use is for the `import user [.zduf]` command!~n"
"Importing the user will only work if you have first imported realm ~ts.~n",
{Load, Ext, Message};
exporter(zpuf, Realm) ->
Load =
fun(KeyName, Acc) ->
case zx_daemon:get_keybin(public, {Realm, KeyName}) of
{ok, Der} ->
Public = {none, Der},
[{KeyName, Public, none} | Acc];
@ -2130,54 +2144,12 @@ create_userfile(Realm, UserName) ->
Acc
end
end,
PubKeyData = lists:foldl(Load, [], Keys),
UserFile = Realm ++ "-" ++ UserName ++ ".zpuf",
Bin = term_to_binary({UserData, PubKeyData}),
ok = file:write_file(UserFile, Bin),
Ext = ".zpuf",
Message =
"Wrote Zomp public user file to ~ts.~n"
"This file can be given to a sysop from ~ts and added to the realm.~n"
"It ONLY contains PUBLIC KEY data.~n",
io:format(Message, [UserFile, Realm]).
-spec export_user() -> ok.
export_user() ->
case pick_realm() of
{error, no_realms} -> {error, "No realms configured.", 61};
Realm -> export_user(Realm)
end.
export_user(Realm) ->
UserName = select_user(Realm),
UserConf = zx_lib:userconf({Realm, UserName}),
{ok, UserData} = file:consult(UserConf),
Keys = proplists:get_value(keys, UserData),
Load =
fun(KeyName, Acc) ->
Key =
case zx_daemon:keybin(private, {Realm, KeyName}) of
{ok, KDer} -> {none, KDer};
_ -> none
end,
Pub =
case zx_daemon:keybin(public, {Realm, KeyName}) of
{ok, PDer} -> {none, PDer};
_ -> none
end,
[{KeyName, Pub, Key} | Acc]
end,
KeyData = lists:foldl(Load, [], Keys),
UserFile = Realm ++ "-" ++ UserName ++ ".zduf",
Bin = term_to_binary({UserData, KeyData}),
ok = file:write_file(UserFile, Bin),
Message =
"Wrote Zomp DANGEROUS user file to ~ts.~n"
"WARNING: This file contains your PRIVATE KEYS and should NEVER be shared with "
"anyone. Its only use is for the `import user [.zduf]` command!~n",
io:format(Message, [UserFile]).
{Load, Ext, Message}.
-spec import_user(file:filename()) -> zx:outcome().
@ -2202,24 +2174,22 @@ import_user2(Bin) ->
{error, "Bad data. Is this really a legitimate .zduf or .zpuf?", 1}
end.
import_user3(NewUserData, KeyData) ->
Realm = proplists:get_value(realm, NewUserData),
UserName = proplists:get_value(username, NewUserData),
UserConf = zx_lib:userconf({Realm, UserName}),
case file:consult(UserConf) of
{ok, OldUserData} ->
UpdatedUserData = merge_userconf(NewUserData, OldUserData, KeyData),
ok = zx_lib:write_terms(UserConf, UpdatedUserData),
import_user3(UserData, KeyData) ->
UserConf = #{realm := Realm, username := UserName} = maps:from_list(UserData),
UserID = {Realm, UserName},
case zx_userconf:load(UserID) of
{ok, OldUserConf} ->
NewUserConf = zx_userconf:merge(UserConf, OldUserConf, KeyData),
ok = zx_userconf:save(NewUserConf),
import_user4(Realm, UserName, KeyData);
{error, enoent} ->
case filelib:is_dir(filename:dirname(UserConf)) of
true ->
ok = zx_lib:write_terms(UserConf, NewUserData),
import_user4(Realm, UserName, KeyData);
false ->
ok = tell(error, "Realm ~tp is not configured.", [Realm]),
{error, bad_realm}
end
{error, bad_user} ->
ok = zx_userconf:save(UserConf),
import_user4(Realm, UserName, KeyData);
{error, bad_realm} ->
ok = tell(error, "Realm ~tp is not configured.", [Realm]),
{error, bad_realm};
Error ->
Error
end.
import_user4(Realm, UserName, Keys) ->
@ -2227,16 +2197,6 @@ import_user4(Realm, UserName, Keys) ->
ok = lists:foreach(Register, Keys),
tell(info, "Imported user ~ts to realm ~ts.", [UserName, Realm]).
merge_userconf(NewUserData, OldUserData, KeyData) ->
NewMap = maps:from_list(NewUserData),
OldMap = maps:from_list(OldUserData),
KeySet = sets:from_list([element(1, K) || K <- KeyData]),
NewKeySet = sets:from_list(maps:get(keys, NewMap)),
OldKeySet = sets:from_list(maps:get(keys, OldMap)),
UltimateKeySet = sets:intersection([KeySet, NewKeySet, OldKeySet]),
Final = maps:put(keys, sets:to_list(UltimateKeySet), maps:merge(OldMap, NewMap)),
maps:to_list(Final).
-spec pick_realm() -> Result
when Result :: {ok, zx:realm()}
@ -2534,7 +2494,7 @@ create_realmfile(Realm) ->
{ok, RealmConf} = zx_lib:load_realm_conf(Realm),
ok = tell("Realm found, creating realm file..."),
KeyName = maps:get(key, RealmConf),
{ok, PubDER} = zx_daemon:keybin(public, {Realm, KeyName}),
{ok, PubDER} = zx_daemon:get_keybin(public, {Realm, KeyName}),
Blob = term_to_binary({RealmConf, PubDER}),
ZRF = Realm ++ ".zrf",
ok = file:write_file(ZRF, Blob),
@ -2716,9 +2676,7 @@ select_user(Realm) ->
| error.
select_private_key(UserID) ->
ConfPath = zx_lib:userconf(UserID),
{ok, UserConf} = file:consult(ConfPath),
Keys = proplists:get_value(keys, UserConf),
{ok, #{keys := Keys}} = zx_userconf:load(UserID),
Realm = element(1, UserID),
select_private_key2(Realm, Keys).

View File

@ -0,0 +1,87 @@
%%% @doc
%%% ZX UserConf
%%%
%%% Handling user configuration data.
%%% @end
-module(zx_userconf).
-vsn("0.3.0").
-author("Craig Everett <zxq9@zxq9.com>").
-copyright("Craig Everett <zxq9@zxq9.com>").
-license("GPL-3.0").
-export([new/0, save/1, load/1, merge/2, merge/3, path/1]).
-type data() :: #{realm := string(),
username := string(),
realname := string(),
contact_info := [zx:contact_info()],
keys := [binary()]}.
new() ->
#{realm => "",
username => "",
realname => "",
contact_info => "",
keys => []}.
-spec save(data()) -> ok | {error, file:posix()}.
save(Data = #{realm := Realm, username := UserName}) ->
Path = path({Realm, UserName}),
ok = filelib:ensure_dir(Path),
Terms = maps:to_list(Data),
zx_lib:write_terms(Path, Terms).
-spec load(UserID) -> Result
when UserID :: zx:user_id(),
Result :: {ok, zx:userconf()}
| {error, Reason},
Reason :: bad_realm
| bad_user
| file:posix().
load(UserID = {Realm, _}) ->
case zx_lib:realm_exists(Realm) of
true -> load2(UserID);
false -> {error, bad_realm}
end.
load2(UserID) ->
case file:consult(path(UserID)) of
{ok, Terms} -> {ok, maps:from_list(Terms)};
{error, enoent} -> {error, bad_user};
Error -> Error
end.
-spec merge(New, Old) -> Merged
when New :: data(),
Old :: data(),
Merged :: data().
merge(New, Old) ->
merge(New, Old, []).
-spec merge(New, Old, Keys) -> Merged
when New :: data(),
Old :: data(),
Keys :: [zx:key_data()],
Merged :: data().
merge(New, Old, KeyData) ->
KeySet = sets:from_list([element(1, K) || K <- KeyData]),
NewKeySet = sets:from_list(maps:get(keys, New)),
OldKeySet = sets:from_list(maps:get(keys, Old)),
UltimateKeySet = sets:intersection([KeySet, NewKeySet, OldKeySet]),
maps:put(keys, sets:to_list(UltimateKeySet), maps:merge(Old, New)).
-spec path(zx:user_id()) -> file:filename().
path({Realm, UserName}) ->
filename:join(zx_lib:path(etc, Realm), UserName ++ ".user").