mirror of
https://github.com/processone/ejabberd.git
synced 2024-11-22 16:20:52 +01:00
Cleanup acme_challenge.erl, move types and records in ejabberd_acme.hrl
This commit is contained in:
parent
133d2ae6d5
commit
1d1250b056
15
include/ejabberd_acme.hrl
Normal file
15
include/ejabberd_acme.hrl
Normal file
@ -0,0 +1,15 @@
|
|||||||
|
|
||||||
|
-record(challenge, {
|
||||||
|
type = <<"http-01">> :: bitstring(),
|
||||||
|
status = pending :: pending | valid | invalid,
|
||||||
|
uri = <<"">> :: bitstring(),
|
||||||
|
token = <<"">> :: bitstring()
|
||||||
|
}).
|
||||||
|
|
||||||
|
-type nonce() :: string().
|
||||||
|
-type url() :: string().
|
||||||
|
-type proplist() :: [{_, _}].
|
||||||
|
-type jws() :: map().
|
||||||
|
-type handle_resp_fun() :: fun(({ok, proplist(), proplist()}) -> {ok, _, nonce()}).
|
||||||
|
|
||||||
|
-type acme_challenge() :: #challenge{}.
|
@ -1,8 +1,7 @@
|
|||||||
-module(acme_challenge).
|
-module(acme_challenge).
|
||||||
|
|
||||||
-export ([ key_authorization/2
|
-export ([ key_authorization/2
|
||||||
, challenges_to_objects/1
|
, solve_challenge/3
|
||||||
, solve_challenges/2
|
|
||||||
]).
|
]).
|
||||||
%% Challenge Types
|
%% Challenge Types
|
||||||
%% ================
|
%% ================
|
||||||
@ -10,52 +9,79 @@
|
|||||||
%% 2. dns-01: https://tools.ietf.org/html/draft-ietf-acme-acme-05#section-7.3
|
%% 2. dns-01: https://tools.ietf.org/html/draft-ietf-acme-acme-05#section-7.3
|
||||||
%% 3. tls-sni-01: https://tools.ietf.org/html/draft-ietf-acme-acme-05#section-7.4
|
%% 3. tls-sni-01: https://tools.ietf.org/html/draft-ietf-acme-acme-05#section-7.4
|
||||||
%% 4. (?) oob-01: https://tools.ietf.org/html/draft-ietf-acme-acme-05#section-7.5
|
%% 4. (?) oob-01: https://tools.ietf.org/html/draft-ietf-acme-acme-05#section-7.5
|
||||||
-define(DEFAULT_HTTP_DIR, "../test-server-for-acme").
|
|
||||||
|
|
||||||
-record(challenge, {
|
-include("ejabberd.hrl").
|
||||||
type = <<"http-01">> :: bitstring(),
|
-include("logger.hrl").
|
||||||
status = pending :: pending | valid | invalid,
|
-include("xmpp.hrl").
|
||||||
uri = <<"">> :: bitstring(),
|
|
||||||
token = <<"">> :: bitstring()
|
|
||||||
}).
|
|
||||||
|
|
||||||
|
-include("ejabberd_acme.hrl").
|
||||||
|
|
||||||
|
-spec parse_challenge(string(), jose_jwk:key()) -> bitstring().
|
||||||
key_authorization(Token, Key) ->
|
key_authorization(Token, Key) ->
|
||||||
Thumbprint = jose_jwk:thumbprint(Key),
|
Thumbprint = jose_jwk:thumbprint(Key),
|
||||||
io:format("Thumbprint: ~p~n", [Thumbprint]),
|
% ?INFO_MSG("Thumbprint: ~p~n", [Thumbprint]),
|
||||||
|
|
||||||
KeyAuthorization = erlang:iolist_to_binary([Token, <<".">>, Thumbprint]),
|
KeyAuthorization = erlang:iolist_to_binary([Token, <<".">>, Thumbprint]),
|
||||||
% io:format("KeyAuthorization: ~p~n", [KeyAuthorization]),
|
KeyAuthorization.
|
||||||
|
|
||||||
KeyAuthorization.
|
-spec parse_challenge({proplist()}) -> {ok, acme_challenge()} | {error, _}.
|
||||||
|
parse_challenge(Challenge0) ->
|
||||||
challenges_to_objects(Challenges) ->
|
try
|
||||||
[clean_challenge(X) || {X} <- Challenges].
|
{Challenge} = Challenge0,
|
||||||
|
{<<"type">>,Type} = proplists:lookup(<<"type">>, Challenge),
|
||||||
clean_challenge(Challenge) ->
|
{<<"status">>,Status} = proplists:lookup(<<"status">>, Challenge),
|
||||||
{<<"type">>,Type} = proplists:lookup(<<"type">>, Challenge),
|
{<<"uri">>,Uri} = proplists:lookup(<<"uri">>, Challenge),
|
||||||
{<<"status">>,Status} = proplists:lookup(<<"status">>, Challenge),
|
{<<"token">>,Token} = proplists:lookup(<<"token">>, Challenge),
|
||||||
{<<"uri">>,Uri} = proplists:lookup(<<"uri">>, Challenge),
|
Res = #challenge{
|
||||||
{<<"token">>,Token} = proplists:lookup(<<"token">>, Challenge),
|
type = Type,
|
||||||
#challenge{
|
status = list_to_atom(bitstring_to_list(Status)),
|
||||||
type = Type,
|
uri = Uri,
|
||||||
status = list_to_atom(bitstring_to_list(Status)),
|
token = Token
|
||||||
uri = Uri,
|
},
|
||||||
token = Token
|
{ok, Res}
|
||||||
}.
|
catch
|
||||||
|
_:Error ->
|
||||||
solve_challenges(Challenges, Key) ->
|
{error, Error}
|
||||||
[solve_challenge(X, Key) || X <- Challenges].
|
end.
|
||||||
|
|
||||||
|
|
||||||
solve_challenge(Chal = #challenge{type = <<"http-01">>, token=Tkn}, Key) ->
|
|
||||||
io:format("Http Challenge: ~p~n", [Chal]),
|
-spec solve_challenge(bitstring(), [{proplist()}], _) -> {ok, url(), bitstring()} | {error, _}.
|
||||||
|
solve_challenge(ChallengeType, Challenges, Options) ->
|
||||||
|
ParsedChallenges = [parse_challenge(Chall) || Chall <- Challenges],
|
||||||
|
case lists:any(fun is_error/1, ParsedChallenges) of
|
||||||
|
true ->
|
||||||
|
?ERROR_MSG("Error parsing challenges: ~p~n", [Challenges]),
|
||||||
|
{error, parse_challenge};
|
||||||
|
false ->
|
||||||
|
case [C || {ok, C} <- ParsedChallenges, is_challenge_type(ChallengeType, C)] of
|
||||||
|
[Challenge] ->
|
||||||
|
solve_challenge1(Challenge, Options);
|
||||||
|
_ ->
|
||||||
|
?ERROR_MSG("Challenge ~p not found in challenges: ~p~n", [ChallengeType, Challenges]),
|
||||||
|
{error, not_found}
|
||||||
|
end
|
||||||
|
end.
|
||||||
|
|
||||||
|
-spec solve_challenge1(acme_challenge(), _) -> {ok, url(), bitstring()} | {error, _}.
|
||||||
|
solve_challenge1(Chal = #challenge{type = <<"http-01">>, token=Tkn}, {Key, HttpDir}) ->
|
||||||
KeyAuthz = key_authorization(Tkn, Key),
|
KeyAuthz = key_authorization(Tkn, Key),
|
||||||
io:format("KeyAuthorization: ~p~n", [KeyAuthz]),
|
FileLocation = HttpDir ++ "/.well-known/acme-challenge/" ++ bitstring_to_list(Tkn),
|
||||||
|
case file:write_file(FileLocation, KeyAuthz) of
|
||||||
|
ok ->
|
||||||
|
{ok, Chal#challenge.uri, KeyAuthz};
|
||||||
|
{error, _} = Err ->
|
||||||
|
?ERROR_MSG("Error writing to file: ~s with reason: ~p~n", [FileLocation, Err]),
|
||||||
|
Err
|
||||||
|
end;
|
||||||
|
solve_challenge1(Challenge, _Key) ->
|
||||||
|
?INFO_MSG("Challenge: ~p~n", [Challenge]).
|
||||||
|
|
||||||
%% Create file for authorization
|
%% Useful functions
|
||||||
ok = file:write_file(?DEFAULT_HTTP_DIR ++
|
|
||||||
"/.well-known/acme-challenge/" ++
|
is_challenge_type(DesiredType, #challenge{type = Type}) when DesiredType =:= Type ->
|
||||||
bitstring_to_list(Tkn), KeyAuthz),
|
true;
|
||||||
{<<"http-01">>, Chal#challenge.uri, KeyAuthz};
|
is_challenge_type(_DesiredType, #challenge{type = _Type}) ->
|
||||||
solve_challenge(Challenge, Key) ->
|
false.
|
||||||
io:format("Challenge: ~p~n", [Challenge]).
|
|
||||||
|
is_error({error, _}) -> true;
|
||||||
|
is_error(_) -> false.
|
@ -1,8 +1,6 @@
|
|||||||
-module (ejabberd_acme).
|
-module (ejabberd_acme).
|
||||||
|
|
||||||
-export([ scenario/3
|
-export([ directory/1
|
||||||
, scenario0/1
|
|
||||||
, directory/1
|
|
||||||
|
|
||||||
, get_account/3
|
, get_account/3
|
||||||
, new_account/4
|
, new_account/4
|
||||||
@ -12,23 +10,21 @@
|
|||||||
|
|
||||||
, new_authz/4
|
, new_authz/4
|
||||||
, get_authz/1
|
, get_authz/1
|
||||||
|
|
||||||
|
, scenario/3
|
||||||
|
, scenario0/2
|
||||||
]).
|
]).
|
||||||
|
|
||||||
-include("ejabberd.hrl").
|
-include("ejabberd.hrl").
|
||||||
-include("logger.hrl").
|
-include("logger.hrl").
|
||||||
-include("xmpp.hrl").
|
-include("xmpp.hrl").
|
||||||
|
|
||||||
|
-include("ejabberd_acme.hrl").
|
||||||
-include_lib("public_key/include/public_key.hrl").
|
-include_lib("public_key/include/public_key.hrl").
|
||||||
|
|
||||||
-define(REQUEST_TIMEOUT, 5000). % 5 seconds.
|
-define(REQUEST_TIMEOUT, 5000). % 5 seconds.
|
||||||
|
|
||||||
|
|
||||||
-type nonce() :: string().
|
|
||||||
-type url() :: string().
|
|
||||||
-type proplist() :: [{_, _}].
|
|
||||||
-type jws() :: map().
|
|
||||||
-type handle_resp_fun() :: fun(({ok, proplist(), proplist()}) -> {ok, _, nonce()}).
|
|
||||||
|
|
||||||
|
|
||||||
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
|
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
|
||||||
%%
|
%%
|
||||||
%% Get Directory
|
%% Get Directory
|
||||||
@ -163,6 +159,12 @@ get_tos(Head) ->
|
|||||||
none
|
none
|
||||||
end.
|
end.
|
||||||
|
|
||||||
|
-spec get_challenges(proplist()) -> [{proplist()}].
|
||||||
|
get_challenges(Body) ->
|
||||||
|
{<<"challenges">>, Challenges} = proplists:lookup(<<"challenges">>, Body),
|
||||||
|
Challenges.
|
||||||
|
|
||||||
|
|
||||||
%% TODO: Fix the duplicated code at the below 4 functions
|
%% TODO: Fix the duplicated code at the below 4 functions
|
||||||
|
|
||||||
-spec make_post_request(url(), bitstring()) ->
|
-spec make_post_request(url(), bitstring()) ->
|
||||||
@ -229,21 +231,6 @@ prepare_get_request(Url, HandleRespFun) ->
|
|||||||
Error
|
Error
|
||||||
end.
|
end.
|
||||||
|
|
||||||
-spec sign_json_jose(jose_jwk:key(), string()) -> jws().
|
|
||||||
sign_json_jose(Key, Json) ->
|
|
||||||
PubKey = jose_jwk:to_public(Key),
|
|
||||||
{_, BinaryPubKey} = jose_jwk:to_binary(PubKey),
|
|
||||||
PubKeyJson = jiffy:decode(BinaryPubKey),
|
|
||||||
% Jws object containing the algorithm
|
|
||||||
%% TODO: Dont hardcode the alg
|
|
||||||
JwsObj = jose_jws:from(
|
|
||||||
#{ <<"alg">> => <<"ES256">>
|
|
||||||
% , <<"b64">> => true
|
|
||||||
, <<"jwk">> => PubKeyJson
|
|
||||||
}),
|
|
||||||
%% Signed Message
|
|
||||||
jose_jws:sign(Key, Json, JwsObj).
|
|
||||||
|
|
||||||
-spec sign_json_jose(jose_jwk:key(), string(), nonce()) -> jws().
|
-spec sign_json_jose(jose_jwk:key(), string(), nonce()) -> jws().
|
||||||
sign_json_jose(Key, Json, Nonce) ->
|
sign_json_jose(Key, Json, Nonce) ->
|
||||||
% Generate a public key
|
% Generate a public key
|
||||||
@ -321,6 +308,7 @@ scenario(CAUrl, AccId, PrivateKey) ->
|
|||||||
|
|
||||||
AccURL = CAUrl ++ "/acme/reg/" ++ AccId,
|
AccURL = CAUrl ++ "/acme/reg/" ++ AccId,
|
||||||
{ok, {_TOS, Account}, Nonce1} = get_account(AccURL, PrivateKey, Nonce0),
|
{ok, {_TOS, Account}, Nonce1} = get_account(AccURL, PrivateKey, Nonce0),
|
||||||
|
?INFO_MSG("Account: ~p~n", [Account]),
|
||||||
|
|
||||||
#{"new-authz" := NewAuthz} = Dirs,
|
#{"new-authz" := NewAuthz} = Dirs,
|
||||||
Req =
|
Req =
|
||||||
@ -335,7 +323,7 @@ scenario(CAUrl, AccId, PrivateKey) ->
|
|||||||
{Account, Authz, PrivateKey}.
|
{Account, Authz, PrivateKey}.
|
||||||
|
|
||||||
|
|
||||||
new_user_scenario(CAUrl) ->
|
new_user_scenario(CAUrl, HttpDir) ->
|
||||||
PrivateKey = generate_key(),
|
PrivateKey = generate_key(),
|
||||||
|
|
||||||
DirURL = CAUrl ++ "/directory",
|
DirURL = CAUrl ++ "/directory",
|
||||||
@ -381,6 +369,12 @@ new_user_scenario(CAUrl) ->
|
|||||||
|
|
||||||
{ok, Authz2, Nonce5} = get_authz(AuthzUrl),
|
{ok, Authz2, Nonce5} = get_authz(AuthzUrl),
|
||||||
|
|
||||||
|
Challenges = get_challenges(Authz2),
|
||||||
|
?INFO_MSG("Challenges: ~p~n", [Challenges]),
|
||||||
|
|
||||||
|
{ok, ChallengeUrl, KeyAuthz} = acme_challenge:solve_challenge(<<"http-01">>, Challenges, {PrivateKey, HttpDir}),
|
||||||
|
?INFO_MSG("File for http-01 challenge written correctly", []),
|
||||||
|
|
||||||
{Account2, Authz2, PrivateKey}.
|
{Account2, Authz2, PrivateKey}.
|
||||||
|
|
||||||
|
|
||||||
@ -388,9 +382,8 @@ generate_key() ->
|
|||||||
jose_jwk:generate_key({ec, secp256r1}).
|
jose_jwk:generate_key({ec, secp256r1}).
|
||||||
|
|
||||||
%% Just a test
|
%% Just a test
|
||||||
scenario0(KeyFile) ->
|
scenario0(KeyFile, HttpDir) ->
|
||||||
PrivateKey = jose_jwk:from_file(KeyFile),
|
PrivateKey = jose_jwk:from_file(KeyFile),
|
||||||
scenario("http://localhost:4000", "2", PrivateKey).
|
% scenario("http://localhost:4000", "2", PrivateKey).
|
||||||
% new_user_scenario("http://localhost:4000").
|
new_user_scenario("http://localhost:4000", HttpDir).
|
||||||
|
|
||||||
% ejabberd_acme:scenario0("/home/konstantinos/Desktop/Programming/ejabberd/private_key_temporary").
|
|
||||||
|
Loading…
Reference in New Issue
Block a user