mirror of
https://github.com/processone/ejabberd.git
synced 2024-11-26 16:26:24 +01:00
Solve http-01 challenge
This commit is contained in:
parent
ddb043aa71
commit
df5d673e63
61
src/acme_challenge.erl
Normal file
61
src/acme_challenge.erl
Normal file
@ -0,0 +1,61 @@
|
||||
-module(acme_challenge).
|
||||
|
||||
-export ([ key_authorization/2
|
||||
, challenges_to_objects/1
|
||||
, solve_challenges/2
|
||||
]).
|
||||
%% Challenge Types
|
||||
%% ================
|
||||
%% 1. http-01: https://tools.ietf.org/html/draft-ietf-acme-acme-05#section-7.2
|
||||
%% 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
|
||||
%% 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, {
|
||||
type = <<"http-01">> :: bitstring(),
|
||||
status = pending :: pending | valid | invalid,
|
||||
uri = <<"">> :: bitstring(),
|
||||
token = <<"">> :: bitstring()
|
||||
}).
|
||||
|
||||
key_authorization(Token, Key) ->
|
||||
Thumbprint = jose_jwk:thumbprint(Key),
|
||||
io:format("Thumbprint: ~p~n", [Thumbprint]),
|
||||
|
||||
KeyAuthorization = erlang:iolist_to_binary([Token, <<".">>, Thumbprint]),
|
||||
% io:format("KeyAuthorization: ~p~n", [KeyAuthorization]),
|
||||
|
||||
KeyAuthorization.
|
||||
|
||||
challenges_to_objects(Challenges) ->
|
||||
[clean_challenge(X) || {X} <- Challenges].
|
||||
|
||||
clean_challenge(Challenge) ->
|
||||
{<<"type">>,Type} = proplists:lookup(<<"type">>, Challenge),
|
||||
{<<"status">>,Status} = proplists:lookup(<<"status">>, Challenge),
|
||||
{<<"uri">>,Uri} = proplists:lookup(<<"uri">>, Challenge),
|
||||
{<<"token">>,Token} = proplists:lookup(<<"token">>, Challenge),
|
||||
#challenge{
|
||||
type = Type,
|
||||
status = list_to_atom(bitstring_to_list(Status)),
|
||||
uri = Uri,
|
||||
token = Token
|
||||
}.
|
||||
|
||||
solve_challenges(Challenges, Key) ->
|
||||
[solve_challenge(X, Key) || X <- Challenges].
|
||||
|
||||
|
||||
solve_challenge(Chal = #challenge{type = <<"http-01">>, token=Tkn}, Key) ->
|
||||
io:format("Http Challenge: ~p~n", [Chal]),
|
||||
KeyAuthz = key_authorization(Tkn, Key),
|
||||
io:format("KeyAuthorization: ~p~n", [KeyAuthz]),
|
||||
|
||||
%% Create file for authorization
|
||||
ok = file:write_file(?DEFAULT_HTTP_DIR ++
|
||||
"/.well-known/acme-challenge/" ++
|
||||
bitstring_to_list(Tkn), KeyAuthz),
|
||||
{<<"http-01">>, Chal#challenge.uri, KeyAuthz};
|
||||
solve_challenge(Challenge, Key) ->
|
||||
io:format("Challenge: ~p~n", [Challenge]).
|
167
src/mod_acme.erl
167
src/mod_acme.erl
@ -32,18 +32,34 @@
|
||||
-include("logger.hrl").
|
||||
|
||||
-include("xmpp.hrl").
|
||||
-include_lib("public_key/include/public_key.hrl").
|
||||
-include_lib("public_key/include/public_key.hrl").
|
||||
|
||||
% -define(CA_URL, "https://acme-v01.api.letsencrypt.org").
|
||||
-define(CA_URL, "https://acme-staging.api.letsencrypt.org").
|
||||
% -define(CA_URL, "http://localhost:4000").
|
||||
|
||||
|
||||
|
||||
-define(DEFAULT_DIRECTORY, ?CA_URL ++ "/directory").
|
||||
-define(DEFAULT_NEW_NONCE, ?CA_URL ++ "/acme/new_nonce").
|
||||
-define(DEFAULT_ACCOUNT, "2273801").
|
||||
|
||||
-define(DEFAULT_KEY_FILE, "private_key_temporary").
|
||||
|
||||
|
||||
|
||||
|
||||
-define(LOCAL_TESTING, true).
|
||||
|
||||
-ifdef(LOCAL_TESTING).
|
||||
-define(CA_URL, "http://localhost:4000").
|
||||
-define(DEFAULT_ACCOUNT, "2").
|
||||
-define(DEFAULT_TOS, <<"http://boulder:4000/terms/v1">>).
|
||||
-define(DEFAULT_AUTHZ,
|
||||
<<"http://localhost:4000/acme/authz/XDAfMW6xBdRogD2-VIfTxlzo4RTlaE2U6x0yrwxnXlw">>).
|
||||
-else.
|
||||
-define(CA_URL, "https://acme-staging.api.letsencrypt.org").
|
||||
-define(DEFAULT_ACCOUNT, "2273801").
|
||||
-define(DEFAULT_TOS, <<"https://letsencrypt.org/documents/LE-SA-v1.1.1-August-1-2016.pdf">>).
|
||||
-define(DEFAULT_AUTHZ, <<"">>).
|
||||
-endif.
|
||||
|
||||
-record(state, {
|
||||
ca_url = ?CA_URL :: list(),
|
||||
@ -94,10 +110,10 @@ get_certificate(Pid, Options) ->
|
||||
ok.
|
||||
|
||||
get_authz(Pid, Options) ->
|
||||
ok.
|
||||
gen_server:call(Pid, ?FUNCTION_NAME).
|
||||
|
||||
complete_challenge(Pid, Options) ->
|
||||
ok.
|
||||
gen_server:call(Pid, {?FUNCTION_NAME, Options}).
|
||||
|
||||
deactivate_authz(Pid, Options) ->
|
||||
ok.
|
||||
@ -127,8 +143,7 @@ handle_call(directory, _From, S = #state{dir_url=Url, dirs=Dirs}) ->
|
||||
{ok, {_Status, Head, Body}} = httpc:request(get, {Url, []}, [], []),
|
||||
|
||||
%% Decode the json string
|
||||
Result = jiffy:decode(Body),
|
||||
{Directories} = Result,
|
||||
{Directories} = jiffy:decode(Body),
|
||||
StrDirectories = [{bitstring_to_list(X), bitstring_to_list(Y)} ||
|
||||
{X,Y} <- Directories],
|
||||
|
||||
@ -137,11 +152,10 @@ handle_call(directory, _From, S = #state{dir_url=Url, dirs=Dirs}) ->
|
||||
Nonce = get_nonce(Head),
|
||||
|
||||
%% Update the directories in state
|
||||
%% TODO: Get the merge of the old and the new dictionary
|
||||
NewDirs = maps:from_list(StrDirectories),
|
||||
% io:format("New directories: ~p~n", [NewDirs]),
|
||||
|
||||
{reply, {ok, Result}, S#state{dirs = NewDirs, nonce = Nonce}};
|
||||
{reply, {ok, {Directories}}, S#state{dirs = NewDirs, nonce = Nonce}};
|
||||
handle_call(new_nonce, _From, S = #state{dirs=Dirs}) ->
|
||||
%% Get url from all directories
|
||||
#{"new_nonce" := Url} = Dirs,
|
||||
@ -166,7 +180,7 @@ handle_call(new_reg, _From, S = #state{ca_url = Ca, dirs=Dirs, nonce = Nonce}) -
|
||||
|
||||
%% Jose
|
||||
{_, SignedBody} = sign_a_json_object_using_jose(Key, ReqBody, Url, Nonce),
|
||||
io:format("Signed Body: ~p~n", [SignedBody]),
|
||||
% io:format("Signed Body: ~p~n", [SignedBody]),
|
||||
|
||||
%% Encode the Signed body with jiffy
|
||||
FinalBody = jiffy:encode(SignedBody),
|
||||
@ -193,7 +207,7 @@ handle_call({account_info, AccountId}, _From, S = #state{ca_url = Ca, dirs=Dirs,
|
||||
|
||||
%% Jose
|
||||
{_, SignedBody} = sign_a_json_object_using_jose(Key, ReqBody, Url, Nonce),
|
||||
io:format("Signed Body: ~p~n", [SignedBody]),
|
||||
% io:format("Signed Body: ~p~n", [SignedBody]),
|
||||
|
||||
%% Encode the Signed body with jiffy
|
||||
FinalBody = jiffy:encode(SignedBody),
|
||||
@ -221,7 +235,7 @@ handle_call({update_account, AccountId}, _From, S = #state{ca_url = Ca, dirs=Dir
|
||||
|
||||
%% Jose
|
||||
{_, SignedBody} = sign_a_json_object_using_jose(Key, ReqBody, Url, Nonce),
|
||||
io:format("Signed Body: ~p~n", [SignedBody]),
|
||||
% io:format("Signed Body: ~p~n", [SignedBody]),
|
||||
|
||||
%% Encode the Signed body with jiffy
|
||||
FinalBody = jiffy:encode(SignedBody),
|
||||
@ -248,7 +262,7 @@ handle_call(new_cert, _From, S = #state{ca_url = Ca, dirs=Dirs, nonce = Nonce})
|
||||
|
||||
%% Jose
|
||||
{_, SignedBody} = sign_a_json_object_using_jose(Key, ReqBody, Url, Nonce),
|
||||
io:format("Signed Body: ~p~n", [SignedBody]),
|
||||
% io:format("Signed Body: ~p~n", [SignedBody]),
|
||||
|
||||
%% Encode the Signed body with jiffy
|
||||
FinalBody = jiffy:encode(SignedBody),
|
||||
@ -266,16 +280,64 @@ handle_call(new_authz, _From, S = #state{ca_url = Ca, dirs=Dirs, nonce = Nonce})
|
||||
#{"new-authz" := Url} = Dirs,
|
||||
|
||||
%% Make the request body
|
||||
ReqBody = jiffy:encode({[
|
||||
{ <<"resource">>, <<"new-authz">>}
|
||||
]}),
|
||||
ReqBody = jiffy:encode({
|
||||
[ { <<"identifier">>, {
|
||||
[ {<<"type">>, <<"dns">>}
|
||||
, {<<"value">>, <<"my-acme-test-ejabberd.com">>}
|
||||
] }}
|
||||
, {<<"existing">>, <<"accept">>}
|
||||
, { <<"resource">>, <<"new-authz">>}
|
||||
] }),
|
||||
|
||||
%% Get the key from a file
|
||||
Key = jose_jwk:from_file(?DEFAULT_KEY_FILE),
|
||||
|
||||
%% Jose
|
||||
{_, SignedBody} = sign_a_json_object_using_jose(Key, ReqBody, Url, Nonce),
|
||||
io:format("Signed Body: ~p~n", [SignedBody]),
|
||||
% io:format("Signed Body: ~p~n", [SignedBody]),
|
||||
|
||||
%% Encode the Signed body with jiffy
|
||||
FinalBody = jiffy:encode(SignedBody),
|
||||
|
||||
%% Post request
|
||||
{ok, {Status, Head, Body}} =
|
||||
httpc:request(post, {Url, [], "application/jose+json", FinalBody}, [], []),
|
||||
|
||||
% Get and save the new nonce
|
||||
NewNonce = get_nonce(Head),
|
||||
|
||||
{reply, {ok, {Status, Head, Body}}, S#state{nonce=NewNonce}};
|
||||
handle_call(get_authz, _From, S = #state{ca_url = Ca, dirs=Dirs, nonce = Nonce}) ->
|
||||
%% Get url from all directories
|
||||
Url = bitstring_to_list(?DEFAULT_AUTHZ),
|
||||
|
||||
%% Post request
|
||||
{ok, {Status, Head, Body}} =
|
||||
% httpc:request(post, {Url, [], "application/jose+json", FinalBody}, [], []),
|
||||
httpc:request(Url),
|
||||
|
||||
% Get and save the new nonce
|
||||
NewNonce = get_nonce(Head),
|
||||
|
||||
{reply, {ok, {Status, Head, Body}}, S#state{nonce=NewNonce}};
|
||||
handle_call({complete_challenge, [Solution]}, _From, S = #state{ca_url = Ca, dirs=Dirs, nonce = Nonce}) ->
|
||||
%% Get url from all directories
|
||||
{ChallengeType, BitUrl, KeyAuthz} = Solution,
|
||||
Url = bitstring_to_list(BitUrl),
|
||||
|
||||
%% Make the request body
|
||||
ReqBody = jiffy:encode({
|
||||
[ { <<"keyAuthorization">>, KeyAuthz}
|
||||
, {<<"type">>, ChallengeType}
|
||||
, { <<"resource">>, <<"challenge">>}
|
||||
] }),
|
||||
|
||||
%% Get the key from a file
|
||||
Key = jose_jwk:from_file(?DEFAULT_KEY_FILE),
|
||||
|
||||
%% Jose
|
||||
{_, SignedBody} = sign_a_json_object_using_jose(Key, ReqBody, Url, Nonce),
|
||||
% io:format("Signed Body: ~p~n", [SignedBody]),
|
||||
|
||||
%% Encode the Signed body with jiffy
|
||||
FinalBody = jiffy:encode(SignedBody),
|
||||
@ -314,6 +376,10 @@ get_nonce(Head) ->
|
||||
{"replay-nonce", Nonce} = proplists:lookup("replay-nonce", Head),
|
||||
Nonce.
|
||||
|
||||
get_challenges({Body}) ->
|
||||
{<<"challenges">>, Challenges} = proplists:lookup(<<"challenges">>, Body),
|
||||
Challenges.
|
||||
|
||||
|
||||
%% Test
|
||||
|
||||
@ -321,7 +387,6 @@ generate_key() ->
|
||||
% Generate a key for now
|
||||
Key = jose_jwk:generate_key({ec, secp256r1}),
|
||||
io:format("Key: ~p~n", [Key]),
|
||||
|
||||
Key.
|
||||
|
||||
sign_a_json_object_using_jose(Key, Json, Url, Nonce) ->
|
||||
@ -331,17 +396,11 @@ sign_a_json_object_using_jose(Key, Json, Url, Nonce) ->
|
||||
{_, BinaryPubKey} = jose_jwk:to_binary(PubKey),
|
||||
% io:format("Public Key: ~p~n", [BinaryPubKey]),
|
||||
PubKeyJson = jiffy:decode(BinaryPubKey),
|
||||
io:format("Public Key: ~p~n", [PubKeyJson]),
|
||||
|
||||
% KeyOkp = jose_jwk:to_okp(Key),
|
||||
% io:format("Key Okp: ~p~n", [KeyOkp]),
|
||||
|
||||
% io:format("Public Key: ~p~n", [PubKeyJson]),
|
||||
|
||||
% Jws object containing the algorithm
|
||||
JwsObj = jose_jws:from(
|
||||
#{
|
||||
% <<"alg">> => <<"HS256">>
|
||||
<<"alg">> => <<"ES256">>
|
||||
#{ <<"alg">> => <<"ES256">>
|
||||
%% Im not sure if it is needed
|
||||
% , <<"b64">> => true
|
||||
, <<"jwk">> => PubKeyJson
|
||||
@ -349,14 +408,13 @@ sign_a_json_object_using_jose(Key, Json, Url, Nonce) ->
|
||||
}),
|
||||
% io:format("Jws: ~p~n", [JwsObj]),
|
||||
|
||||
|
||||
%% Signed Message
|
||||
Signed = jose_jws:sign(Key, Json, JwsObj),
|
||||
% io:format("Signed: ~p~n", [Signed]),
|
||||
|
||||
%% Peek protected
|
||||
Protected = jose_jws:peek_protected(Signed),
|
||||
io:format("Protected: ~p~n", [jiffy:decode(Protected)]),
|
||||
% io:format("Protected: ~p~n", [jiffy:decode(Protected)]),
|
||||
|
||||
%% Peek Payload
|
||||
Payload = jose_jws:peek_payload(Signed),
|
||||
@ -369,16 +427,16 @@ sign_a_json_object_using_jose(Key, Json, Url, Nonce) ->
|
||||
Signed.
|
||||
|
||||
scenario() ->
|
||||
% scenario_new_account().
|
||||
scenario_old_account().
|
||||
|
||||
scenario_old_account() ->
|
||||
{ok, Pid} = start(),
|
||||
io:format("Server started: ~p~n", [Pid]),
|
||||
|
||||
{ok, Result} = directory(Pid, []),
|
||||
io:format("Directory result: ~p~n", [Result]),
|
||||
|
||||
% %% Request the creation of a new account
|
||||
% {ok, {Status, Head, Body}} = new_reg(Pid, []),
|
||||
% io:format("New account~nHead: ~p~nBody: ~p~n", [{Status, Head}, jiffy:decode(Body)]),
|
||||
|
||||
%% Get the info of an existing account
|
||||
% {ok, {Status1, Head1, Body1}} = account_info(Pid, ?DEFAULT_ACCOUNT),
|
||||
% io:format("Account: ~p~nHead: ~p~nBody: ~p~n",
|
||||
@ -389,14 +447,47 @@ scenario() ->
|
||||
io:format("Account: ~p~nHead: ~p~nBody: ~p~n",
|
||||
[?DEFAULT_ACCOUNT, {Status1, Head1}, jiffy:decode(Body1)]),
|
||||
|
||||
%% New authorization
|
||||
% {ok, {Status2, Head2, Body2}} = new_authz(Pid, []),
|
||||
% io:format("New Authz~nHead: ~p~nBody: ~p~n",
|
||||
% [{Status2, Head2}, jiffy:decode(Body2)]),
|
||||
|
||||
%% Get authorization
|
||||
{ok, {Status2, Head2, Body2}} = get_authz(Pid, []),
|
||||
io:format("Get Authz~nHead: ~p~nBody: ~p~n",
|
||||
[{Status2, Head2}, jiffy:decode(Body2)]),
|
||||
|
||||
% Challenges = get_challenges(jiffy:decode(Body2)),
|
||||
% % io:format("Challenges: ~p~n", [Challenges]),
|
||||
|
||||
% ChallengeObjects = acme_challenge:challenges_to_objects(Challenges),
|
||||
% % io:format("Challenges: ~p~n", [ChallengeObjects]),
|
||||
|
||||
% %% Create a key-authorization
|
||||
% Key = jose_jwk:from_file(?DEFAULT_KEY_FILE),
|
||||
% % acme_challenge:key_authorization(<<"pipi">>, Key),
|
||||
|
||||
% Solutions = acme_challenge:solve_challenges(ChallengeObjects, Key),
|
||||
% io:format("Solutions: ~p~n", [Solutions]),
|
||||
|
||||
% {ok, {Status3, Head3, Body3}} =
|
||||
% complete_challenge(Pid, [X || X <- Solutions, X =/= ok]),
|
||||
% io:format("Complete_challenge~nHead: ~p~nBody: ~p~n",
|
||||
% [{Status3, Head3}, jiffy:decode(Body3)]),
|
||||
|
||||
%% New certification
|
||||
% {ok, {Status2, Head2, Body2}} = new_cert(Pid, []),
|
||||
% io:format("New Cert~nHead: ~p~nBody: ~p~n",
|
||||
% [{Status2, Head2}, jiffy:decode(Body2)]),
|
||||
|
||||
%% New authorization
|
||||
{ok, {Status2, Head2, Body2}} = new_authz(Pid, []),
|
||||
io:format("New Authz~nHead: ~p~nBody: ~p~n",
|
||||
[{Status2, Head2}, jiffy:decode(Body2)]),
|
||||
ok.
|
||||
|
||||
scenario_new_account() ->
|
||||
{ok, Pid} = start(),
|
||||
io:format("Server started: ~p~n", [Pid]),
|
||||
|
||||
{ok, Result} = directory(Pid, []),
|
||||
io:format("Directory result: ~p~n", [Result]),
|
||||
|
||||
%% Request the creation of a new account
|
||||
{ok, {Status, Head, Body}} = new_reg(Pid, []),
|
||||
io:format("New account~nHead: ~p~nBody: ~p~n", [{Status, Head}, jiffy:decode(Body)]).
|
Loading…
Reference in New Issue
Block a user