-
Notifications
You must be signed in to change notification settings - Fork 16
/
Copy pathacme-v2.escript
executable file
·154 lines (125 loc) · 4.93 KB
/
acme-v2.escript
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
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
#!/usr/bin/env escript
%% -*- erlang -*-
%%! -smp enable -sname factorial -mnesia debug verbose
main([Domain]) ->
include_libs(),
shotgun:start(),
io:format("Domain= ~p~n", [letsencrypt_utils:bin(Domain)]),
%halt(1),
CertPath = "/tmp/le/certs",
WwwPath = "/tmp/le/webroot",
Key = letsencrypt_ssl:private_key(undefined, CertPath),
Jws = letsencrypt_jws:init(Key),
%Uri = "https://acme-v02.api.letsencrypt.org/directory",
%Uri = "https://acme-staging-v02.api.letsencrypt.org/directory",
Opts = #{debug => true, netopts => #{timeout => 30000}},
% 1. get directory
{ok, Directory} = letsencrypt_api:directory(staging, Opts),
io:format("directory = ~p~n", [Directory]),
% 2. get first nonce
{ok, Nonce} = letsencrypt_api:nonce(Directory, Opts),
io:format("~p~n", [Nonce]),
%TODO: add contact email (optional) => reuse same account
% requires to save key and use this to sign account query with
%TODO: handle account status: "valid", "deactivated", and "revoked"
%TODO: option to check account status only
%TODO: option to list orders
{ok, Account, Location, Nonce2} = letsencrypt_api:account(Directory, Key, Jws#{nonce => Nonce}, Opts),
io:format("~p, ~p, ~p~n", [Location, Account, Nonce2]),
% build a new Jws with account uri
Jws2 = #{
alg => maps:get(alg, Jws),
nonce => Nonce2,
kid => Location
},
{ok, Order, OrderLocation, Nonce3} = letsencrypt_api:order(Directory,
letsencrypt_utils:bin(Domain), Key, Jws2, Opts),
io:format("~p, ~p, ~p~n", [OrderLocation, Order, Nonce3]),
%TODO: iterate over list
% Order may contains several authorizations urls
AuthzUri = lists:nth(1, maps:get(<<"authorizations">>, Order)),
{ok, Authorization, AuthzLocation, Nonce4} =
letsencrypt_api:authorization(AuthzUri, Key, Jws2#{nonce => Nonce3}, Opts),
io:format("~p, ~p, ~p~n", [AuthzLocation, Authorization, Nonce4]),
% extract http challenge (1st in list)
% TODO: allow choosing challenge to validate
[Challenge] = lists:filter(fun(C) ->
maps:get(<<"type">>, C, error) =:= <<"http-01">>
end,
maps:get(<<"challenges">>, Authorization)
),
io:format("challenge= ~p~n", [Challenge]),
% challenges types
% - http-01
% token
% - dsn-01
% - tls-alpn-01
% status:
% - pending
% - processing
% - valid
% - invalid
% compute thumbprint
AcctKey = maps:get(<<"key">>, Account),
Token = maps:get(<<"token">>, Challenge),
Thumbprint = letsencrypt_jws:keyauth(AcctKey, Token),
io:format("key: ~p, token: ~p, thumb: ~p~n", [AcctKey, Token, Thumbprint]),
% write thumbprint to file
io:format("writing thumbprint file~n"),
{ok, Fd} = file:open(<<(letsencrypt_utils:bin(WwwPath))/binary, "/.well-known/acme-challenge/",
Token/binary>>, [raw, write, binary]),
file:write(Fd, Thumbprint),
file:close(Fd),
% notify server - challenge is ready.
{ok, _, _, Nonce5} = letsencrypt_api:challenge(Challenge, Key, Jws2#{nonce => Nonce4}, Opts),
% wait enough time to let acme server to validate hash file
io:format("wait 20secs~n"),
timer:sleep(20000),
% checking authorization (is challenge validated ?)
% status should be 'valid'
{ok, _, _, Nonce6} = letsencrypt_api:authorization(AuthzUri, Key,
Jws2#{nonce => Nonce5}, Opts),
% build & send CSR (with dedicated private key)
Sans = [],
#{file := KeyFile} = letsencrypt_ssl:private_key({new, Domain ++ ".key"}, CertPath),
Csr = letsencrypt_ssl:cert_request(letsencrypt_utils:str(Domain), CertPath, Sans),
io:format("key= ~p, csr= ~p~n", [KeyFile, Csr]),
% we want 'finalize' value
io:format("finalizing: sending csr~n"),
%
% fsm
% authorization :: status=valid -> order :: status=ready (loop) until ->
%
% finalize(CSR) :: status=processing -> order (loop until) status=ready ->
% status=ready -> returns order object ->
%
% certificate
% returns status processing
% status 'ready'
% order MUST be ready before finalizing
{ok, _, _, Nonce42} = letsencrypt_api:order(OrderLocation, Key, Jws2#{nonce =>
Nonce6}, Opts),
% status 'processing'
% finalize may returns either 'processing' or 'ready'
{ok, _, _, Nonce7} = letsencrypt_api:finalize(Order, Csr, Key,
Jws2#{nonce => Nonce42}, Opts),
% status 'ready'
% order includes 'certificate' link
{ok, FinOrder, _, Nonce8} = letsencrypt_api:order(OrderLocation, Key, Jws2#{nonce =>
Nonce7}, Opts),
%timer:sleep(5000),
%{ok, FinOrder, _, Nonce9} = letsencrypt_api:finalize(Order, Csr, Key,
% Jws2#{nonce => Nonce8}, Opts),
% download certificate
{ok, Cert} = letsencrypt_api:certificate(FinOrder, Key, Jws2#{nonce => Nonce8}, Opts),
io:format("cert= ~p~n", [Cert]),
{ok, Fd2} = file:open(CertPath++"/"++Domain++".crt", [raw, write, binary]),
file:write(Fd2, Cert),
file:close(Fd2),
io:format("DONE"),
ok.
include_libs() ->
BaseDir = filename:dirname(escript:script_name()),
io:format("~p~n", [BaseDir]),
[ code:add_pathz(Path) || Path <- filelib:wildcard(BaseDir++"/_build/default/lib/*/ebin") ],
ok.