aboutsummaryrefslogtreecommitdiff
path: root/src/acme_challenge.erl
blob: 081e10429cd3be2aff34bc96dceae5afb27ed667 (plain) (blame)
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.