aboutsummaryrefslogtreecommitdiff
path: root/src/ejabberd_acme.erl
diff options
context:
space:
mode:
authorKonstantinos Kallas <konstantinos.kallas@hotmail.com>2017-06-09 18:53:54 +0300
committerKonstantinos Kallas <konstantinos.kallas@hotmail.com>2017-06-09 18:53:54 +0300
commit167edacb5f62a0fb394e0eedb34e1b675c9197fc (patch)
tree047e9c8a9871da32613aa5667b1efdc6864f2869 /src/ejabberd_acme.erl
parentImplement some basic account handling functions (diff)
Make Stylistic Changes in order to conform to guidelines:
1. Remove trailing whitespace 2. Remove Macros 3. Handle all erroneous response codes the same way 4. Add specs Also don't return nonces anymore when the http response is negative.
Diffstat (limited to '')
-rw-r--r--src/ejabberd_acme.erl104
1 files changed, 51 insertions, 53 deletions
diff --git a/src/ejabberd_acme.erl b/src/ejabberd_acme.erl
index 517fdf10a..b8671b75d 100644
--- a/src/ejabberd_acme.erl
+++ b/src/ejabberd_acme.erl
@@ -1,7 +1,7 @@
-module (ejabberd_acme).
-export([ scenario/3
- , scenario0/0
+ , scenario0/1
, directory/1
, get_account/3
, new_account/4
@@ -14,12 +14,15 @@
-include_lib("public_key/include/public_key.hrl").
-define(REQUEST_TIMEOUT, 5000). % 5 seconds.
--define(DIRURL, "directory").
--define(REGURL, "/acme/reg/").
--define(DEFAULT_KEY_FILE, "private_key_temporary").
+-type nonce() :: string().
+-type url() :: string().
+-type proplist() :: [{_, _}].
+-type jws() :: map().
+-spec directory(url()) ->
+ {ok, map(), nonce()} | {error, _}.
directory(DirURL) ->
Options = [],
HttpOptions = [{timeout, ?REQUEST_TIMEOUT}],
@@ -27,66 +30,63 @@ directory(DirURL) ->
{ok, {{_, Code, _}, Head, Body}} when Code >= 200, Code =< 299 ->
%% Decode the json string
{Directories} = jiffy:decode(Body),
- StrDirectories = [{bitstring_to_list(X), bitstring_to_list(Y)} ||
+ StrDirectories = [{bitstring_to_list(X), bitstring_to_list(Y)} ||
{X,Y} <- Directories],
% Find and save the replay nonce
Nonce = get_nonce(Head),
%% Return Map of Directories
NewDirs = maps:from_list(StrDirectories),
{ok, NewDirs, Nonce};
- {ok, {{_, Code, _}, Head, _Body}} ->
+ {ok, {{_, Code, _}, _Head, _Body}} ->
?ERROR_MSG("Got unexpected status code from <~s>: ~B",
[DirURL, Code]),
- Nonce = get_nonce(Head),
- {error, unexpected_code, Nonce};
+ {error, unexpected_code};
{error, Reason} ->
?ERROR_MSG("Error requesting directory from <~s>: ~p",
[DirURL, Reason]),
{error, Reason}
end.
+-spec new_account(url(), jose_jwk:key(), proplist(), nonce()) ->
+ {ok, {url(), proplist()}, nonce()} | {error, _}.
new_account(NewAccURl, PrivateKey, Req, Nonce) ->
%% Make the request body
ReqBody = jiffy:encode({[{ <<"resource">>, <<"new-reg">>}] ++ Req}),
- {_, SignedBody} = sign_json_jose(PrivateKey, ReqBody, NewAccURl, Nonce),
+ {_, SignedBody} = sign_json_jose(PrivateKey, ReqBody, Nonce),
%% Encode the Signed body with jiffy
FinalBody = jiffy:encode(SignedBody),
Options = [],
HttpOptions = [{timeout, ?REQUEST_TIMEOUT}],
- case httpc:request(post,
+ case httpc:request(post,
{NewAccURl, [], "application/jose+json", FinalBody}, HttpOptions, Options) of
{ok, {{_, Code, _}, Head, Body}} when Code >= 200, Code =< 299 ->
%% Decode the json string
{Return} = jiffy:decode(Body),
- TOSUrl = get_TOS(Head),
+ TOSUrl = get_tos(Head),
% Find and save the replay nonce
NewNonce = get_nonce(Head),
{ok, {TOSUrl, Return}, NewNonce};
- {ok, {{_, 409 = Code, _}, Head, Body}} ->
- ?ERROR_MSG("Got status code: ~B from <~s>, Body: ~s",
- [NewAccURl, Code, Body]),
- NewNonce = get_nonce(Head),
- {error, key_in_use, NewNonce};
- {ok, {{_, Code, _}, Head, Body}} ->
+ {ok, {{_, Code, _}, _Head, Body}} ->
?ERROR_MSG("Got unexpected status code from <~s>: ~B, Body: ~s",
[NewAccURl, Code, Body]),
- NewNonce = get_nonce(Head),
- {error, unexpected_code, NewNonce};
+ {error, unexpected_code};
{error, Reason} ->
?ERROR_MSG("Error requesting directory from <~s>: ~p",
[NewAccURl, Reason]),
- {error, Reason, Nonce}
+ {error, Reason}
end.
+-spec update_account(url(), jose_jwk:key(), proplist(), nonce()) ->
+ {ok, proplist(), nonce()} | {error, _}.
update_account(AccURl, PrivateKey, Req, Nonce) ->
%% Make the request body
ReqBody = jiffy:encode({[{ <<"resource">>, <<"reg">>}] ++ Req}),
- {_, SignedBody} = sign_json_jose(PrivateKey, ReqBody, AccURl, Nonce),
+ {_, SignedBody} = sign_json_jose(PrivateKey, ReqBody, Nonce),
%% Encode the Signed body with jiffy
FinalBody = jiffy:encode(SignedBody),
Options = [],
HttpOptions = [{timeout, ?REQUEST_TIMEOUT}],
- case httpc:request(post,
+ case httpc:request(post,
{AccURl, [], "application/jose+json", FinalBody}, HttpOptions, Options) of
{ok, {{_, Code, _}, Head, Body}} when Code >= 200, Code =< 299 ->
%% Decode the json string
@@ -94,79 +94,78 @@ update_account(AccURl, PrivateKey, Req, Nonce) ->
% Find and save the replay nonce
NewNonce = get_nonce(Head),
{ok, Return, NewNonce};
- {ok, {{_, Code, _}, Head, Body}} ->
+ {ok, {{_, Code, _}, _Head, Body}} ->
?ERROR_MSG("Got unexpected status code from <~s>: ~B, Body: ~s",
[AccURl, Code, Body]),
- NewNonce = get_nonce(Head),
- {error, unexpected_code, NewNonce};
+ {error, unexpected_code};
{error, Reason} ->
?ERROR_MSG("Error requesting directory from <~s>: ~p",
[AccURl, Reason]),
- {error, Reason, Nonce}
+ {error, Reason}
end.
-
+-spec get_account(url(), jose_jwk:key(), nonce()) ->
+ {ok, {url(), proplist()}, nonce()} | {error, _}.
get_account(AccURl, PrivateKey, Nonce) ->
%% Make the request body
- ReqBody = jiffy:encode({[
- { <<"resource">>, <<"reg">>}
- ]}),
+ ReqBody = jiffy:encode({[{<<"resource">>, <<"reg">>}]}),
%% Jose Sign
- {_, SignedBody} = sign_json_jose(PrivateKey, ReqBody, AccURl, Nonce),
+ {_, SignedBody} = sign_json_jose(PrivateKey, ReqBody, Nonce),
%% Encode the Signed body with jiffy
FinalBody = jiffy:encode(SignedBody),
Options = [],
HttpOptions = [{timeout, ?REQUEST_TIMEOUT}],
- case httpc:request(post,
+ case httpc:request(post,
{AccURl, [], "application/jose+json", FinalBody}, HttpOptions, Options) of
{ok, {{_, Code, _}, Head, Body}} when Code >= 200, Code =< 299 ->
%% Decode the json string
{Return} = jiffy:decode(Body),
- TOSUrl = get_TOS(Head),
+ TOSUrl = get_tos(Head),
% Find and save the replay nonce
NewNonce = get_nonce(Head),
{ok, {TOSUrl, Return}, NewNonce};
- {ok, {{_, Code, _}, Head, Body}} ->
+ {ok, {{_, Code, _}, _Head, Body}} ->
?ERROR_MSG("Got unexpected status code from <~s>: ~B, Head: ~s",
[AccURl, Code, Body]),
- NewNonce = get_nonce(Head),
- {error, unexpected_code, NewNonce};
+ {error, unexpected_code};
{error, Reason} ->
?ERROR_MSG("Error requesting directory from <~s>: ~p",
[AccURl, Reason]),
- {error, Reason, Nonce}
+ {error, Reason}
end.
%%
%% Useful funs
%%
-
+-spec get_nonce(proplist()) -> nonce() | 'none'.
get_nonce(Head) ->
- {"replay-nonce", Nonce} = proplists:lookup("replay-nonce", Head),
- Nonce.
+ case proplists:lookup("replay-nonce", Head) of
+ {"replay-nonce", Nonce} -> Nonce;
+ none -> none
+ end.
%% Very bad way to extract this
%% TODO: Find a better way
-get_TOS(Head) ->
+-spec get_tos(proplist()) -> url() | 'none'.
+get_tos(Head) ->
try
- [{_, Link}] = [{K, V} || {K, V} <- Head,
+ [{_, Link}] = [{K, V} || {K, V} <- Head,
K =:= "link" andalso lists:suffix("\"terms-of-service\"", V)],
[Link1, _] = string:tokens(Link, ";"),
Link2 = string:strip(Link1, left, $<),
string:strip(Link2, right, $>)
catch
_:_ ->
- no_tos
+ none
end.
-
-sign_json_jose(Key, Json, Url, Nonce) ->
+-spec sign_json_jose(jose_jwk:key(), string(), nonce()) -> jws().
+sign_json_jose(Key, Json, Nonce) ->
% Generate a public key
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(
@@ -185,17 +184,16 @@ sign_json_jose(Key, Json, Url, Nonce) ->
%% A typical acme workflow
scenario(CAUrl, AccId, PrivateKey) ->
-
- DirURL = CAUrl ++ "/" ++ ?DIRURL,
+ DirURL = CAUrl ++ "/directory",
{ok, Dirs, Nonce0} = directory(DirURL),
- AccURL = CAUrl ++ ?REGURL ++ AccId,
+ AccURL = CAUrl ++ "/acme/reg/" ++ AccId,
{ok, {_TOS, Account}, Nonce1} = get_account(AccURL, PrivateKey, Nonce0).
new_user_scenario(CAUrl) ->
PrivateKey = generate_key(),
- DirURL = CAUrl ++ "/" ++ ?DIRURL,
+ DirURL = CAUrl ++ "/directory",
{ok, Dirs, Nonce0} = directory(DirURL),
#{"new-reg" := NewAccURL} = Dirs,
@@ -203,7 +201,7 @@ new_user_scenario(CAUrl) ->
{ok, {TOS, Account}, Nonce1} = new_account(NewAccURL, PrivateKey, Req0, Nonce0),
{_, AccId} = proplists:lookup(<<"id">>, Account),
- AccURL = CAUrl ++ ?REGURL ++ integer_to_list(AccId),
+ AccURL = CAUrl ++ "/acme/reg/" ++ integer_to_list(AccId),
Req1 = [{ <<"agreement">>, list_to_bitstring(TOS)}],
{ok, Account1, Nonce2} = update_account(AccURL, PrivateKey, Req1, Nonce1),
@@ -213,7 +211,7 @@ generate_key() ->
jose_jwk:generate_key({ec, secp256r1}).
%% Just a test
-scenario0() ->
- PrivateKey = jose_jwk:from_file(?DEFAULT_KEY_FILE),
+scenario0(KeyFile) ->
+ PrivateKey = jose_jwk:from_file(KeyFile),
% scenario("http://localhost:4000", "2", PrivateKey).
new_user_scenario("http://localhost:4000").