1.2.5
by Sergei Golovan
Import upstream version 13.b.4-dfsg |
1 |
%%
|
2 |
%% %CopyrightBegin%
|
|
3 |
%%
|
|
4 |
%% Copyright Ericsson AB 2007-2009. All Rights Reserved.
|
|
5 |
%%
|
|
6 |
%% The contents of this file are subject to the Erlang Public License,
|
|
7 |
%% Version 1.1, (the "License"); you may not use this file except in
|
|
8 |
%% compliance with the License. You should have received a copy of the
|
|
9 |
%% Erlang Public License along with this software. If not, it can be
|
|
10 |
%% retrieved online at http://www.erlang.org/.
|
|
11 |
%%
|
|
12 |
%% Software distributed under the License is distributed on an "AS IS"
|
|
13 |
%% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See
|
|
14 |
%% the License for the specific language governing rights and limitations
|
|
15 |
%% under the License.
|
|
16 |
%%
|
|
17 |
%% %CopyrightEnd%
|
|
18 |
%%
|
|
19 |
||
20 |
-module(make_certs). |
|
21 |
||
22 |
-export([all/2]). |
|
23 |
||
24 |
-record(dn, {commonName, |
|
25 |
organizationalUnitName = "Erlang OTP", |
|
26 |
organizationName = "Ericsson AB", |
|
27 |
localityName = "Stockholm", |
|
28 |
countryName = "SE", |
|
29 |
emailAddress = "peter@erix.ericsson.se"}). |
|
30 |
||
31 |
all(DataDir, PrivDir) -> |
|
32 |
OpenSSLCmd = "openssl", |
|
33 |
create_rnd(DataDir, PrivDir), % For all requests |
|
34 |
rootCA(PrivDir, OpenSSLCmd, "erlangCA"), |
|
35 |
intermediateCA(PrivDir, OpenSSLCmd, "otpCA", "erlangCA"), |
|
36 |
endusers(PrivDir, OpenSSLCmd, "otpCA", ["client", "server"]), |
|
37 |
collect_certs(PrivDir, ["erlangCA", "otpCA"], ["client", "server"]), |
|
38 |
%% Create keycert files
|
|
39 |
SDir = filename:join([PrivDir, "server"]), |
|
40 |
SC = filename:join([SDir, "cert.pem"]), |
|
41 |
SK = filename:join([SDir, "key.pem"]), |
|
42 |
SKC = filename:join([SDir, "keycert.pem"]), |
|
43 |
append_files([SK, SC], SKC), |
|
44 |
CDir = filename:join([PrivDir, "client"]), |
|
45 |
CC = filename:join([CDir, "cert.pem"]), |
|
46 |
CK = filename:join([CDir, "key.pem"]), |
|
47 |
CKC = filename:join([CDir, "keycert.pem"]), |
|
48 |
append_files([CK, CC], CKC), |
|
49 |
remove_rnd(PrivDir). |
|
50 |
||
51 |
append_files(FileNames, ResultFileName) -> |
|
52 |
{ok, ResultFile} = file:open(ResultFileName, [write]), |
|
53 |
do_append_files(FileNames, ResultFile). |
|
54 |
||
55 |
do_append_files([], RF) -> |
|
56 |
ok = file:close(RF); |
|
57 |
do_append_files([F|Fs], RF) -> |
|
58 |
{ok, Data} = file:read_file(F), |
|
59 |
ok = file:write(RF, Data), |
|
60 |
do_append_files(Fs, RF). |
|
61 |
||
62 |
rootCA(Root, OpenSSLCmd, Name) -> |
|
63 |
create_ca_dir(Root, Name, ca_cnf(Name)), |
|
64 |
DN = #dn{commonName = Name}, |
|
65 |
create_self_signed_cert(Root, OpenSSLCmd, Name, req_cnf(DN)), |
|
66 |
ok. |
|
67 |
||
68 |
intermediateCA(Root, OpenSSLCmd, CA, ParentCA) -> |
|
69 |
CA = "otpCA", |
|
70 |
create_ca_dir(Root, CA, ca_cnf(CA)), |
|
71 |
CARoot = filename:join([Root, CA]), |
|
72 |
DN = #dn{commonName = CA}, |
|
73 |
CnfFile = filename:join([CARoot, "req.cnf"]), |
|
74 |
file:write_file(CnfFile, req_cnf(DN)), |
|
75 |
KeyFile = filename:join([CARoot, "private", "key.pem"]), |
|
76 |
ReqFile = filename:join([CARoot, "req.pem"]), |
|
77 |
create_req(Root, OpenSSLCmd, CnfFile, KeyFile, ReqFile), |
|
78 |
CertFile = filename:join([CARoot, "cert.pem"]), |
|
79 |
sign_req(Root, OpenSSLCmd, ParentCA, "ca_cert", ReqFile, CertFile). |
|
80 |
||
81 |
endusers(Root, OpenSSLCmd, CA, Users) -> |
|
82 |
lists:foreach(fun(User) -> enduser(Root, OpenSSLCmd, CA, User) end, Users). |
|
83 |
||
84 |
enduser(Root, OpenSSLCmd, CA, User) -> |
|
85 |
UsrRoot = filename:join([Root, User]), |
|
86 |
file:make_dir(UsrRoot), |
|
87 |
CnfFile = filename:join([UsrRoot, "req.cnf"]), |
|
88 |
DN = #dn{commonName = User}, |
|
89 |
file:write_file(CnfFile, req_cnf(DN)), |
|
90 |
KeyFile = filename:join([UsrRoot, "key.pem"]), |
|
91 |
ReqFile = filename:join([UsrRoot, "req.pem"]), |
|
92 |
create_req(Root, OpenSSLCmd, CnfFile, KeyFile, ReqFile), |
|
93 |
CertFile = filename:join([UsrRoot, "cert.pem"]), |
|
94 |
sign_req(Root, OpenSSLCmd, CA, "user_cert", ReqFile, CertFile). |
|
95 |
||
96 |
collect_certs(Root, CAs, Users) -> |
|
97 |
Bins = lists:foldr( |
|
98 |
fun(CA, Acc) -> |
|
99 |
File = filename:join([Root, CA, "cert.pem"]), |
|
100 |
{ok, Bin} = file:read_file(File), |
|
101 |
[Bin, "\n" | Acc] |
|
102 |
end, [], CAs), |
|
103 |
lists:foreach( |
|
104 |
fun(User) -> |
|
105 |
File = filename:join([Root, User, "cacerts.pem"]), |
|
106 |
file:write_file(File, Bins) |
|
107 |
end, Users). |
|
108 |
||
109 |
create_self_signed_cert(Root, OpenSSLCmd, CAName, Cnf) -> |
|
110 |
CARoot = filename:join([Root, CAName]), |
|
111 |
CnfFile = filename:join([CARoot, "req.cnf"]), |
|
112 |
file:write_file(CnfFile, Cnf), |
|
113 |
KeyFile = filename:join([CARoot, "private", "key.pem"]), |
|
114 |
CertFile = filename:join([CARoot, "cert.pem"]), |
|
115 |
Cmd = [OpenSSLCmd, " req" |
|
116 |
" -new"
|
|
117 |
" -x509"
|
|
118 |
" -config ", CnfFile, |
|
119 |
" -keyout ", KeyFile, |
|
120 |
" -out ", CertFile], |
|
121 |
Env = [{"ROOTDIR", Root}], |
|
122 |
cmd(Cmd, Env). |
|
123 |
||
124 |
create_ca_dir(Root, CAName, Cnf) -> |
|
125 |
CARoot = filename:join([Root, CAName]), |
|
126 |
file:make_dir(CARoot), |
|
127 |
create_dirs(CARoot, ["certs", "crl", "newcerts", "private"]), |
|
128 |
create_rnd(Root, filename:join([CAName, "private"])), |
|
129 |
create_files(CARoot, [{"serial", "01\n"}, |
|
130 |
{"index.txt", ""}, |
|
131 |
{"ca.cnf", Cnf}]). |
|
132 |
||
133 |
create_req(Root, OpenSSLCmd, CnfFile, KeyFile, ReqFile) -> |
|
134 |
Cmd = [OpenSSLCmd, " req" |
|
135 |
" -new"
|
|
136 |
" -config ", CnfFile, |
|
137 |
" -keyout ", KeyFile, |
|
138 |
" -out ", ReqFile], |
|
139 |
Env = [{"ROOTDIR", Root}], |
|
140 |
cmd(Cmd, Env). |
|
141 |
||
142 |
sign_req(Root, OpenSSLCmd, CA, CertType, ReqFile, CertFile) -> |
|
143 |
CACnfFile = filename:join([Root, CA, "ca.cnf"]), |
|
144 |
Cmd = [OpenSSLCmd, " ca" |
|
145 |
" -batch"
|
|
146 |
" -notext"
|
|
147 |
" -config ", CACnfFile, |
|
148 |
" -extensions ", CertType, |
|
149 |
" -in ", ReqFile, |
|
150 |
" -out ", CertFile], |
|
151 |
Env = [{"ROOTDIR", Root}], |
|
152 |
cmd(Cmd, Env). |
|
153 |
||
154 |
%%
|
|
155 |
%% Misc
|
|
156 |
%%
|
|
157 |
||
158 |
create_dirs(Root, Dirs) -> |
|
159 |
lists:foreach(fun(Dir) -> |
|
160 |
file:make_dir(filename:join([Root, Dir])) end, |
|
161 |
Dirs). |
|
162 |
||
163 |
create_files(Root, NameContents) -> |
|
164 |
lists:foreach( |
|
165 |
fun({Name, Contents}) -> |
|
166 |
file:write_file(filename:join([Root, Name]), Contents) end, |
|
167 |
NameContents). |
|
168 |
||
169 |
create_rnd(FromDir, ToDir) -> |
|
170 |
From = filename:join([FromDir, "RAND"]), |
|
171 |
To = filename:join([ToDir, "RAND"]), |
|
172 |
file:copy(From, To). |
|
173 |
||
174 |
remove_rnd(Dir) -> |
|
175 |
File = filename:join([Dir, "RAND"]), |
|
176 |
file:delete(File). |
|
177 |
||
178 |
cmd(Cmd, Env) -> |
|
179 |
FCmd = lists:flatten(Cmd), |
|
180 |
Port = open_port({spawn, FCmd}, [stream, eof, exit_status, stderr_to_stdout, |
|
181 |
{env, Env}]), |
|
182 |
eval_cmd(Port). |
|
183 |
||
184 |
eval_cmd(Port) -> |
|
185 |
receive
|
|
186 |
{Port, {data, _}} -> |
|
187 |
eval_cmd(Port); |
|
188 |
{Port, eof} -> |
|
189 |
ok
|
|
190 |
end, |
|
191 |
receive
|
|
192 |
{Port, {exit_status, Status}} when Status /= 0 -> |
|
193 |
%% io:fwrite("exit status: ~w~n", [Status]),
|
|
194 |
exit({eval_cmd, Status}) |
|
195 |
after 0 -> |
|
196 |
ok
|
|
197 |
end. |
|
198 |
||
199 |
%%
|
|
200 |
%% Contents of configuration files
|
|
201 |
%%
|
|
202 |
||
203 |
req_cnf(DN) -> |
|
204 |
["# Purpose: Configuration for requests (end users and CAs)." |
|
205 |
"\n" |
|
206 |
"ROOTDIR = $ENV::ROOTDIR\n" |
|
207 |
"\n" |
|
208 |
||
209 |
"[req]\n" |
|
210 |
"input_password = secret\n" |
|
211 |
"output_password = secret\n" |
|
212 |
"default_bits = 1024\n" |
|
213 |
"RANDFILE = $ROOTDIR/RAND\n" |
|
214 |
"encrypt_key = no\n" |
|
215 |
"default_md = sha1\n" |
|
216 |
"#string_mask = pkix\n" |
|
217 |
"x509_extensions = ca_ext\n" |
|
218 |
"prompt = no\n" |
|
219 |
"distinguished_name= name\n" |
|
220 |
"\n" |
|
221 |
||
222 |
"[name]\n" |
|
223 |
"commonName = ", DN#dn.commonName, "\n" |
|
224 |
"organizationalUnitName = ", DN#dn.organizationalUnitName, "\n" |
|
225 |
"organizationName = ", DN#dn.organizationName, "\n" |
|
226 |
"localityName = ", DN#dn.localityName, "\n" |
|
227 |
"countryName = ", DN#dn.countryName, "\n" |
|
228 |
"emailAddress = ", DN#dn.emailAddress, "\n" |
|
229 |
"\n" |
|
230 |
||
231 |
"[ca_ext]\n" |
|
232 |
"basicConstraints = critical, CA:true\n" |
|
233 |
"keyUsage = cRLSign, keyCertSign\n" |
|
234 |
"subjectKeyIdentifier = hash\n" |
|
235 |
"subjectAltName = email:copy\n"]. |
|
236 |
||
237 |
||
238 |
ca_cnf(CA) -> |
|
239 |
["# Purpose: Configuration for CAs.\n" |
|
240 |
"\n" |
|
241 |
"ROOTDIR = $ENV::ROOTDIR\n" |
|
242 |
"default_ca = ca\n" |
|
243 |
"\n" |
|
244 |
||
245 |
"[ca]\n" |
|
246 |
"dir = $ROOTDIR/", CA, "\n" |
|
247 |
"certs = $dir/certs\n" |
|
248 |
"crl_dir = $dir/crl\n" |
|
249 |
"database = $dir/index.txt\n" |
|
250 |
"new_certs_dir = $dir/newcerts\n" |
|
251 |
"certificate = $dir/cert.pem\n" |
|
252 |
"serial = $dir/serial\n" |
|
253 |
"crl = $dir/crl.pem\n" |
|
254 |
"private_key = $dir/private/key.pem\n" |
|
255 |
"RANDFILE = $dir/private/RAND\n" |
|
256 |
"\n" |
|
257 |
"x509_extensions = user_cert\n" |
|
258 |
"default_days = 3600\n" |
|
259 |
"default_md = sha1\n" |
|
260 |
"preserve = no\n" |
|
261 |
"policy = policy_match\n" |
|
262 |
"\n" |
|
263 |
||
264 |
"[policy_match]\n" |
|
265 |
"commonName = supplied\n" |
|
266 |
"organizationalUnitName = optional\n" |
|
267 |
"organizationName = match\n" |
|
268 |
"countryName = match\n" |
|
269 |
"localityName = match\n" |
|
270 |
"emailAddress = supplied\n" |
|
271 |
"\n" |
|
272 |
||
273 |
"[user_cert]\n" |
|
274 |
"basicConstraints = CA:false\n" |
|
275 |
"keyUsage = nonRepudiation, digitalSignature, keyEncipherment\n" |
|
276 |
"subjectKeyIdentifier = hash\n" |
|
277 |
"authorityKeyIdentifier = keyid,issuer:always\n" |
|
278 |
"subjectAltName = email:copy\n" |
|
279 |
"issuerAltName = issuer:copy\n" |
|
280 |
"\n" |
|
281 |
||
282 |
"[ca_cert]\n" |
|
283 |
"basicConstraints = critical,CA:true\n" |
|
284 |
"keyUsage = cRLSign, keyCertSign\n" |
|
285 |
"subjectKeyIdentifier = hash\n" |
|
286 |
"authorityKeyIdentifier = keyid:always,issuer:always\n" |
|
287 |
"subjectAltName = email:copy\n" |
|
288 |
"issuerAltName = issuer:copy\n"]. |