1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
|
-module(acme_challenge).
-export ([key_authorization/2,
solve_challenge/3,
process/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
-include("ejabberd.hrl").
-include("logger.hrl").
-include("xmpp.hrl").
-include("ejabberd_http.hrl").
-include("ejabberd_acme.hrl").
%% TODO: Maybe validate request here??
process(LocalPath, Request) ->
Result = ets_get_key_authorization(LocalPath),
?INFO_MSG("Trying to serve: ~p at: ~p", [Request, LocalPath]),
?INFO_MSG("Http Response: ~p", [Result]),
{200,
[{<<"Content-Type">>, <<"text/plain">>}],
Result}.
-spec key_authorization(bitstring(), jose_jwk:key()) -> bitstring().
key_authorization(Token, Key) ->
Thumbprint = jose_jwk:thumbprint(Key),
%% ?INFO_MSG("Thumbprint: ~p~n", [Thumbprint]),
KeyAuthorization = erlang:iolist_to_binary([Token, <<".">>, Thumbprint]),
KeyAuthorization.
-spec parse_challenge({proplist()}) -> {ok, acme_challenge()} | {error, _}.
parse_challenge(Challenge0) ->
try
{Challenge} = Challenge0,
{<<"type">>,Type} = proplists:lookup(<<"type">>, Challenge),
{<<"status">>,Status} = proplists:lookup(<<"status">>, Challenge),
{<<"uri">>,Uri} = proplists:lookup(<<"uri">>, Challenge),
{<<"token">>,Token} = proplists:lookup(<<"token">>, Challenge),
Res =
#challenge{
type = Type,
status = list_to_atom(bitstring_to_list(Status)),
uri = bitstring_to_list(Uri),
token = Token
},
{ok, Res}
catch
_:Error ->
{error, Error}
end.
-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(), {jose_jwk:key(), string()}) ->
{ok, url(), bitstring()} | {error, _}.
solve_challenge1(Chal = #challenge{type = <<"http-01">>, token=Tkn}, Key) ->
KeyAuthz = key_authorization(Tkn, Key),
%% save_key_authorization(Chal, Tkn, KeyAuthz, HttpDir);
ets_put_key_authorization(Tkn, KeyAuthz),
{ok, Chal#challenge.uri, KeyAuthz};
solve_challenge1(Challenge, _Key) ->
?INFO_MSG("Challenge: ~p~n", [Challenge]).
save_key_authorization(Chal, Tkn, KeyAuthz, HttpDir) ->
FileLocation = HttpDir ++ "/.well-known/acme-challenge/" ++ bitstring_to_list(Tkn),
case file:write_file(FileLocation, KeyAuthz) of
ok ->
{ok, Chal#challenge.uri, KeyAuthz};
{error, Reason} = Err ->
?ERROR_MSG("Error writing to file: ~s with reason: ~p~n", [FileLocation, Reason]),
Err
end.
-spec ets_put_key_authorization(bitstring(), bitstring()) -> ok.
ets_put_key_authorization(Tkn, KeyAuthz) ->
Tab = ets_get_acme_table(),
Key = [<<"acme-challenge">>, Tkn],
ets:insert(Tab, {Key, KeyAuthz}),
ok.
-spec ets_get_key_authorization([bitstring()]) -> bitstring().
ets_get_key_authorization(Key) ->
Tab = ets_get_acme_table(),
case ets:take(Tab, Key) of
[{Key, KeyAuthz}] ->
KeyAuthz;
_ ->
?ERROR_MSG("Unable to serve key authorization in: ~p", [Key]),
<<"">>
end.
-spec ets_get_acme_table() -> atom().
ets_get_acme_table() ->
case ets:info(acme) of
undefined ->
ets:new(acme, [named_table, public]);
_ ->
acme
end.
%% Useful functions
is_challenge_type(DesiredType, #challenge{type = Type}) when DesiredType =:= Type ->
true;
is_challenge_type(_DesiredType, #challenge{type = _Type}) ->
false.
-spec is_error({'error', _}) -> 'true';
({'ok', _}) -> 'false'.
is_error({error, _}) -> true;
is_error(_) -> false.
|