4
%% Copyright Ericsson AB 2010. All Rights Reserved.
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/.
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
20
%% Create test certificates
22
-module(erl_make_certs).
23
-include_lib("public_key/include/public_key.hrl").
25
-export([make_cert/1, gen_rsa/1, verify_signature/3, write_pem/3]).
28
%%--------------------------------------------------------------------
29
%% @doc Create and return a der encoded certificate
31
%% -------------------------------------------------------
33
%% validity {date(), date() + week()}
35
%% subject [] list of the following content
41
%% {org_unit, OrgUnit}
46
%% issuer = {Issuer, IssuerKey} true (i.e. a ca cert is created)
47
%% (obs IssuerKey migth be {Key, Password}
48
%% key = KeyFile|KeyBin|rsa|dsa Subject PublicKey rsa or dsa generates key
51
%% (OBS: The generated keys are for testing only)
52
%% @spec ([{::atom(), ::term()}]) -> {Cert::binary(), Key::binary()}
54
%%--------------------------------------------------------------------
57
SubjectPrivateKey = get_key(Opts),
58
{TBSCert, IssuerKey} = make_tbs(SubjectPrivateKey, Opts),
59
Cert = public_key:pkix_sign(TBSCert, IssuerKey),
60
true = verify_signature(Cert, IssuerKey, undef), %% verify that the keys where ok
61
{Cert, encode_key(SubjectPrivateKey)}.
63
%%--------------------------------------------------------------------
64
%% @doc Writes pem files in Dir with FileName ++ ".pem" and FileName ++ "_key.pem"
65
%% @spec (::string(), ::string(), {Cert,Key}) -> ok
67
%%--------------------------------------------------------------------
68
write_pem(Dir, FileName, {Cert, Key = {_,_,not_encrypted}}) when is_binary(Cert) ->
69
ok = der_to_pem(filename:join(Dir, FileName ++ ".pem"),
70
[{'Certificate', Cert, not_encrypted}]),
71
ok = der_to_pem(filename:join(Dir, FileName ++ "_key.pem"), [Key]).
73
%%--------------------------------------------------------------------
74
%% @doc Creates a rsa key (OBS: for testing only)
75
%% the size are in bytes
76
%% @spec (::integer()) -> {::atom(), ::binary(), ::opaque()}
78
%%--------------------------------------------------------------------
79
gen_rsa(Size) when is_integer(Size) ->
81
{Key, encode_key(Key)}.
83
%%--------------------------------------------------------------------
84
%% @doc Creates a dsa key (OBS: for testing only)
85
%% the sizes are in bytes
86
%% @spec (::integer()) -> {::atom(), ::binary(), ::opaque()}
88
%%--------------------------------------------------------------------
89
gen_dsa(LSize,NSize) when is_integer(LSize), is_integer(NSize) ->
90
Key = gen_dsa2(LSize, NSize),
91
{Key, encode_key(Key)}.
93
%%--------------------------------------------------------------------
94
%% @doc Verifies cert signatures
95
%% @spec (::binary(), ::tuple()) -> ::boolean()
97
%%--------------------------------------------------------------------
98
verify_signature(DerEncodedCert, DerKey, _KeyParams) ->
99
Key = decode_key(DerKey),
101
#'RSAPrivateKey'{modulus=Mod, publicExponent=Exp} ->
102
public_key:pkix_verify(DerEncodedCert,
103
#'RSAPublicKey'{modulus=Mod, publicExponent=Exp});
104
#'DSAPrivateKey'{p=P, q=Q, g=G, y=Y} ->
105
public_key:pkix_verify(DerEncodedCert, {Y, #'Dss-Parms'{p=P, q=Q, g=G}})
108
%%%%%%%%%%%%%%%%%%%%%%%%% Implementation %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
111
case proplists:get_value(key, Opts) of
112
undefined -> make_key(rsa, Opts);
113
rsa -> make_key(rsa, Opts);
114
dsa -> make_key(dsa, Opts);
116
Password = proplists:get_value(password, Opts, no_passwd),
117
decode_key(Key, Password)
120
decode_key({Key, Pw}) ->
123
decode_key(Key, no_passwd).
126
decode_key(#'RSAPublicKey'{} = Key,_) ->
128
decode_key(#'RSAPrivateKey'{} = Key,_) ->
130
decode_key(#'DSAPrivateKey'{} = Key,_) ->
132
decode_key(PemEntry = {_,_,_}, Pw) ->
133
public_key:pem_entry_decode(PemEntry, Pw);
134
decode_key(PemBin, Pw) ->
135
[KeyInfo] = public_key:pem_decode(PemBin),
136
decode_key(KeyInfo, Pw).
138
encode_key(Key = #'RSAPrivateKey'{}) ->
139
{ok, Der} = 'OTP-PUB-KEY':encode('RSAPrivateKey', Key),
140
{'RSAPrivateKey', list_to_binary(Der), not_encrypted};
141
encode_key(Key = #'DSAPrivateKey'{}) ->
142
{ok, Der} = 'OTP-PUB-KEY':encode('DSAPrivateKey', Key),
143
{'DSAPrivateKey', list_to_binary(Der), not_encrypted}.
145
make_tbs(SubjectKey, Opts) ->
146
Version = list_to_atom("v"++integer_to_list(proplists:get_value(version, Opts, 3))),
148
IssuerProp = proplists:get_value(issuer, Opts, true),
149
{Issuer, IssuerKey} = issuer(IssuerProp, Opts, SubjectKey),
151
{Algo, Parameters} = sign_algorithm(IssuerKey, Opts),
153
SignAlgo = #'SignatureAlgorithm'{algorithm = Algo,
154
parameters = Parameters},
155
Subject = case IssuerProp of
156
true -> %% Is a Root Ca
159
subject(proplists:get_value(subject, Opts),false)
162
{#'OTPTBSCertificate'{serialNumber = trunc(random:uniform()*100000000)*10000 + 1,
163
signature = SignAlgo,
165
validity = validity(Opts),
167
subjectPublicKeyInfo = publickey(SubjectKey),
169
extensions = extensions(Opts)
172
issuer(true, Opts, SubjectKey) ->
174
{subject(proplists:get_value(subject, Opts), true), SubjectKey};
175
issuer({Issuer, IssuerKey}, _Opts, _SubjectKey) when is_binary(Issuer) ->
176
{issuer_der(Issuer), decode_key(IssuerKey)};
177
issuer({File, IssuerKey}, _Opts, _SubjectKey) when is_list(File) ->
178
{ok, [{cert, Cert, _}|_]} = public_key:pem_to_der(File),
179
{issuer_der(Cert), decode_key(IssuerKey)}.
181
issuer_der(Issuer) ->
182
Decoded = public_key:pkix_decode_cert(Issuer, otp),
183
#'OTPCertificate'{tbsCertificate=Tbs} = Decoded,
184
#'OTPTBSCertificate'{subject=Subject} = Tbs,
187
subject(undefined, IsRootCA) ->
188
User = if IsRootCA -> "RootCA"; true -> os:getenv("USER") end,
189
Opts = [{email, User ++ "@erlang.org"},
194
{org_unit, "testing dep"}],
199
subject(SubjectOpts) when is_list(SubjectOpts) ->
201
{Type,Value} = subject_enc(Opt),
202
[#'AttributeTypeAndValue'{type=Type, value=Value}]
204
{rdnSequence, [Encode(Opt) || Opt <- SubjectOpts]}.
206
%% Fill in the blanks
207
subject_enc({name, Name}) -> {?'id-at-commonName', {printableString, Name}};
208
subject_enc({email, Email}) -> {?'id-emailAddress', Email};
209
subject_enc({city, City}) -> {?'id-at-localityName', {printableString, City}};
210
subject_enc({state, State}) -> {?'id-at-stateOrProvinceName', {printableString, State}};
211
subject_enc({org, Org}) -> {?'id-at-organizationName', {printableString, Org}};
212
subject_enc({org_unit, OrgUnit}) -> {?'id-at-organizationalUnitName', {printableString, OrgUnit}};
213
subject_enc({country, Country}) -> {?'id-at-countryName', Country};
214
subject_enc({serial, Serial}) -> {?'id-at-serialNumber', Serial};
215
subject_enc({title, Title}) -> {?'id-at-title', {printableString, Title}};
216
subject_enc({dnQualifer, DnQ}) -> {?'id-at-dnQualifier', DnQ};
217
subject_enc(Other) -> Other.
221
case proplists:get_value(extensions, Opts, []) of
225
lists:flatten([extension(Ext) || Ext <- default_extensions(Exts)])
228
default_extensions(Exts) ->
229
Def = [{key_usage,undefined},
230
{subject_altname, undefined},
231
{issuer_altname, undefined},
232
{basic_constraints, default},
233
{name_constraints, undefined},
234
{policy_constraints, undefined},
235
{ext_key_usage, undefined},
236
{inhibit_any, undefined},
237
{auth_key_id, undefined},
238
{subject_key_id, undefined},
239
{policy_mapping, undefined}],
240
Filter = fun({Key, _}, D) -> lists:keydelete(Key, 1, D) end,
241
Exts ++ lists:foldl(Filter, Def, Exts).
243
extension({_, undefined}) -> [];
244
extension({basic_constraints, Data}) ->
247
#'Extension'{extnID = ?'id-ce-basicConstraints',
248
extnValue = #'BasicConstraints'{cA=true},
252
Len when is_integer(Len) ->
253
#'Extension'{extnID = ?'id-ce-basicConstraints',
254
extnValue = #'BasicConstraints'{cA=true, pathLenConstraint=Len},
257
#'Extension'{extnID = ?'id-ce-basicConstraints',
260
extension({Id, Data, Critical}) ->
261
#'Extension'{extnID = Id, extnValue = Data, critical = Critical}.
264
publickey(#'RSAPrivateKey'{modulus=N, publicExponent=E}) ->
265
Public = #'RSAPublicKey'{modulus=N, publicExponent=E},
266
Algo = #'PublicKeyAlgorithm'{algorithm= ?rsaEncryption, parameters='NULL'},
267
#'OTPSubjectPublicKeyInfo'{algorithm = Algo,
268
subjectPublicKey = Public};
269
publickey(#'DSAPrivateKey'{p=P, q=Q, g=G, y=Y}) ->
270
Algo = #'PublicKeyAlgorithm'{algorithm= ?'id-dsa',
271
parameters={params, #'Dss-Parms'{p=P, q=Q, g=G}}},
272
#'OTPSubjectPublicKeyInfo'{algorithm = Algo, subjectPublicKey = Y}.
275
DefFrom0 = calendar:gregorian_days_to_date(calendar:date_to_gregorian_days(date())-1),
276
DefTo0 = calendar:gregorian_days_to_date(calendar:date_to_gregorian_days(date())+7),
277
{DefFrom, DefTo} = proplists:get_value(validity, Opts, {DefFrom0, DefTo0}),
278
Format = fun({Y,M,D}) -> lists:flatten(io_lib:format("~w~2..0w~2..0w000000Z",[Y,M,D])) end,
279
#'Validity'{notBefore={generalTime, Format(DefFrom)},
280
notAfter ={generalTime, Format(DefTo)}}.
282
sign_algorithm(#'RSAPrivateKey'{}, Opts) ->
283
Type = case proplists:get_value(digest, Opts, sha1) of
284
sha1 -> ?'sha1WithRSAEncryption';
285
sha512 -> ?'sha512WithRSAEncryption';
286
sha384 -> ?'sha384WithRSAEncryption';
287
sha256 -> ?'sha256WithRSAEncryption';
288
md5 -> ?'md5WithRSAEncryption';
289
md2 -> ?'md2WithRSAEncryption'
292
sign_algorithm(#'DSAPrivateKey'{p=P, q=Q, g=G}, _Opts) ->
293
{?'id-dsa-with-sha1', {params,#'Dss-Parms'{p=P, q=Q, g=G}}}.
295
make_key(rsa, _Opts) ->
296
%% (OBS: for testing only)
298
make_key(dsa, _Opts) ->
299
gen_dsa2(128, 20). %% Bytes i.e. {1024, 160}
301
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
302
%% RSA key generation (OBS: for testing only)
303
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
305
-define(SMALL_PRIMES, [65537,97,89,83,79,73,71,67,61,59,53,
306
47,43,41,37,31,29,23,19,17,13,11,7,5,3]).
312
Tot = (P - 1) * (Q - 1),
313
[E|_] = lists:dropwhile(fun(Candidate) -> (Tot rem Candidate) == 0 end, ?SMALL_PRIMES),
314
{D1,D2} = extended_gcd(E, Tot),
315
D = erlang:max(D1,D2),
320
{Co1,Co2} = extended_gcd(Q, P),
321
Co = erlang:max(Co1,Co2),
322
#'RSAPrivateKey'{version = 'two-prime',
328
exponent1 = D rem (P-1),
329
exponent2 = D rem (Q-1),
334
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
335
%% DSA key generation (OBS: for testing only)
336
%% See http://en.wikipedia.org/wiki/Digital_Signature_Algorithm
337
%% and the fips_186-3.pdf
338
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
339
gen_dsa2(LSize, NSize) ->
340
Q = prime(NSize), %% Choose N-bit prime Q
342
P0 = prime((LSize div 2) +1),
344
%% Choose L-bit prime modulus P such that p–1 is a multiple of q.
345
case dsa_search(X0 div (2*Q*P0), P0, Q, 1000) of
347
gen_dsa2(LSize, NSize);
349
G = crypto:mod_exp(2, (P-1) div Q, P), % Choose G a number whose multiplicative order modulo p is q.
350
%% such that This may be done by setting g = h^(p–1)/q mod p, commonly h=2 is used.
352
X = prime(20), %% Choose x by some random method, where 0 < x < q.
353
Y = crypto:mod_exp(G, X, P), %% Calculate y = g^x mod p.
355
#'DSAPrivateKey'{version=0, p=P, q=Q, g=G, y=Y, x=X}
358
%% See fips_186-3.pdf
359
dsa_search(T, P0, Q, Iter) when Iter > 0 ->
361
case is_prime(crypto:mpint(P), 50) of
363
false -> dsa_search(T+1, P0, Q, Iter-1)
365
dsa_search(_,_,_,_) ->
369
%%%%%%% Crypto Math %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
371
Rand = odd_rand(ByteSize),
372
crypto:erlint(prime_odd(Rand, 0)).
374
prime_odd(Rand, N) ->
375
case is_prime(Rand, 50) of
379
NotPrime = crypto:erlint(Rand),
380
prime_odd(crypto:mpint(NotPrime+2), N+1)
383
%% see http://en.wikipedia.org/wiki/Fermat_primality_test
384
is_prime(_, 0) -> true;
385
is_prime(Candidate, Test) ->
386
CoPrime = odd_rand(<<0,0,0,4, 10000:32>>, Candidate),
387
case crypto:mod_exp(CoPrime, Candidate, Candidate) of
388
CoPrime -> is_prime(Candidate, Test-1);
393
Min = 1 bsl (Size*8-1),
394
Max = (1 bsl (Size*8))-1,
395
odd_rand(crypto:mpint(Min), crypto:mpint(Max)).
398
Rand = <<Sz:32, _/binary>> = crypto:rand_uniform(Min,Max),
399
BitSkip = (Sz+4)*8-1,
401
Odd = <<_:BitSkip, 1:1>> -> Odd;
402
Even = <<_:BitSkip, 0:1>> ->
403
crypto:mpint(crypto:erlint(Even)+1)
406
extended_gcd(A, B) ->
411
{X, Y} = extended_gcd(B, N),
416
{ok, PemBin} = file:read_file(File),
417
public_key:pem_decode(PemBin).
419
der_to_pem(File, Entries) ->
420
PemBin = public_key:pem_encode(Entries),
421
file:write_file(File, PemBin).