1%% 2%% %CopyrightBegin% 3%% 4%% Copyright Ericsson AB 2011-2017. All Rights Reserved. 5%% 6%% Licensed under the Apache License, Version 2.0 (the "License"); 7%% you may not use this file except in compliance with the License. 8%% You may obtain a copy of the License at 9%% 10%% http://www.apache.org/licenses/LICENSE-2.0 11%% 12%% Unless required by applicable law or agreed to in writing, software 13%% distributed under the License is distributed on an "AS IS" BASIS, 14%% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15%% See the License for the specific language governing permissions and 16%% limitations under the License. 17%% 18%% %CopyrightEnd% 19%% 20 21%% Create test certificates 22 23-module(erl_make_certs). 24-include_lib("public_key/include/public_key.hrl"). 25 26-export([make_cert/1, gen_rsa/1, verify_signature/3, write_pem/3, 27 gen_dsa/2, gen_ec/1, 28 pem_to_der/1, der_to_pem/2 29 ]). 30 31%%-------------------------------------------------------------------- 32%% @doc Create and return a der encoded certificate 33%% Option Default 34%% ------------------------------------------------------- 35%% digest sha1 36%% validity {date(), date() + week()} 37%% version 3 38%% subject [] list of the following content 39%% {name, Name} 40%% {email, Email} 41%% {city, City} 42%% {state, State} 43%% {org, Org} 44%% {org_unit, OrgUnit} 45%% {country, Country} 46%% {serial, Serial} 47%% {title, Title} 48%% {dnQualifer, DnQ} 49%% issuer = {Issuer, IssuerKey} true (i.e. a ca cert is created) 50%% (obs IssuerKey migth be {Key, Password} 51%% key = KeyFile|KeyBin|rsa|dsa|ec Subject PublicKey rsa, dsa or ec generates key 52%% 53%% 54%% (OBS: The generated keys are for testing only) 55%% @spec ([{::atom(), ::term()}]) -> {Cert::binary(), Key::binary()} 56%% @end 57%%-------------------------------------------------------------------- 58 59make_cert(Opts) -> 60 SubjectPrivateKey = get_key(Opts), 61 {TBSCert, IssuerKey} = make_tbs(SubjectPrivateKey, Opts), 62 Cert = public_key:pkix_sign(TBSCert, IssuerKey), 63 true = verify_signature(Cert, IssuerKey, undef), %% verify that the keys where ok 64 {Cert, encode_key(SubjectPrivateKey)}. 65 66%%-------------------------------------------------------------------- 67%% @doc Writes pem files in Dir with FileName ++ ".pem" and FileName ++ "_key.pem" 68%% @spec (::string(), ::string(), {Cert,Key}) -> ok 69%% @end 70%%-------------------------------------------------------------------- 71write_pem(Dir, FileName, {Cert, Key = {_,_,not_encrypted}}) when is_binary(Cert) -> 72 ok = der_to_pem(filename:join(Dir, FileName ++ ".pem"), 73 [{'Certificate', Cert, not_encrypted}]), 74 ok = der_to_pem(filename:join(Dir, FileName ++ "_key.pem"), [Key]). 75 76%%-------------------------------------------------------------------- 77%% @doc Creates a rsa key (OBS: for testing only) 78%% the size are in bytes 79%% @spec (::integer()) -> {::atom(), ::binary(), ::opaque()} 80%% @end 81%%-------------------------------------------------------------------- 82gen_rsa(Size) when is_integer(Size) -> 83 Key = gen_rsa2(Size), 84 {Key, encode_key(Key)}. 85 86%%-------------------------------------------------------------------- 87%% @doc Creates a dsa key (OBS: for testing only) 88%% the sizes are in bytes 89%% @spec (::integer()) -> {::atom(), ::binary(), ::opaque()} 90%% @end 91%%-------------------------------------------------------------------- 92gen_dsa(LSize,NSize) when is_integer(LSize), is_integer(NSize) -> 93 Key = gen_dsa2(LSize, NSize), 94 {Key, encode_key(Key)}. 95 96%%-------------------------------------------------------------------- 97%% @doc Creates a ec key (OBS: for testing only) 98%% the sizes are in bytes 99%% @spec (::integer()) -> {::atom(), ::binary(), ::opaque()} 100%% @end 101%%-------------------------------------------------------------------- 102gen_ec(Curve) when is_atom(Curve) -> 103 Key = gen_ec2(Curve), 104 {Key, encode_key(Key)}. 105 106%%-------------------------------------------------------------------- 107%% @doc Verifies cert signatures 108%% @spec (::binary(), ::tuple()) -> ::boolean() 109%% @end 110%%-------------------------------------------------------------------- 111verify_signature(DerEncodedCert, DerKey, _KeyParams) -> 112 Key = decode_key(DerKey), 113 case Key of 114 {#'RSAPrivateKey'{modulus=Mod, publicExponent=Exp}, #'RSASSA-PSS-params'{}=P} -> 115 public_key:pkix_verify(DerEncodedCert, 116 {#'RSAPublicKey'{modulus=Mod, publicExponent=Exp}, P}); 117 #'RSAPrivateKey'{modulus=Mod, publicExponent=Exp} -> 118 public_key:pkix_verify(DerEncodedCert, 119 #'RSAPublicKey'{modulus=Mod, publicExponent=Exp}); 120 #'DSAPrivateKey'{p=P, q=Q, g=G, y=Y} -> 121 public_key:pkix_verify(DerEncodedCert, {Y, #'Dss-Parms'{p=P, q=Q, g=G}}); 122 #'ECPrivateKey'{version = _Version, privateKey = _PrivKey, 123 parameters = Params, publicKey = PubKey} -> 124 public_key:pkix_verify(DerEncodedCert, {#'ECPoint'{point = PubKey}, Params}) 125 end. 126 127%%%%%%%%%%%%%%%%%%%%%%%%% Implementation %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% 128 129get_key(Opts) -> 130 case proplists:get_value(key, Opts) of 131 undefined -> make_key(rsa, Opts); 132 rsa -> make_key(rsa, Opts); 133 dsa -> make_key(dsa, Opts); 134 ec -> make_key(ec, Opts); 135 Key -> 136 Password = proplists:get_value(password, Opts, no_passwd), 137 decode_key(Key, Password) 138 end. 139 140decode_key({#'RSAPrivateKey'{},#'RSASSA-PSS-params'{}}=Key) -> 141 Key; 142decode_key({Key, Pw}) -> 143 decode_key(Key, Pw); 144decode_key(Key) -> 145 decode_key(Key, no_passwd). 146 147 148decode_key(#'RSAPublicKey'{} = Key,_) -> 149 Key; 150decode_key(#'RSAPrivateKey'{} = Key,_) -> 151 Key; 152decode_key({#'RSAPrivateKey'{},#'RSASSA-PSS-params'{}} = Key,_) -> 153 Key; 154decode_key(#'DSAPrivateKey'{} = Key,_) -> 155 Key; 156decode_key(#'ECPrivateKey'{} = Key,_) -> 157 Key; 158decode_key(PemEntry = {_,_,_}, Pw) -> 159 public_key:pem_entry_decode(PemEntry, Pw); 160decode_key(PemBin, Pw) -> 161 [KeyInfo] = public_key:pem_decode(PemBin), 162 decode_key(KeyInfo, Pw). 163 164encode_key(Key = #'RSAPrivateKey'{}) -> 165 {ok, Der} = 'OTP-PUB-KEY':encode('RSAPrivateKey', Key), 166 {'RSAPrivateKey', Der, not_encrypted}; 167encode_key(Key = {#'RSAPrivateKey'{},#'RSASSA-PSS-params'{}}) -> 168 Der = public_key:der_encode('PrivateKeyInfo', Key), 169 {'PrivateKeyInfo', Der, not_encrypted}; 170encode_key(Key = #'DSAPrivateKey'{}) -> 171 {ok, Der} = 'OTP-PUB-KEY':encode('DSAPrivateKey', Key), 172 {'DSAPrivateKey', Der, not_encrypted}; 173encode_key(Key = #'ECPrivateKey'{}) -> 174 {ok, Der} = 'OTP-PUB-KEY':encode('ECPrivateKey', Key), 175 {'ECPrivateKey', Der, not_encrypted}. 176 177make_tbs(SubjectKey, Opts) -> 178 Version = list_to_atom("v"++integer_to_list(proplists:get_value(version, Opts, 3))), 179 180 IssuerProp = proplists:get_value(issuer, Opts, true), 181 {Issuer, IssuerKey} = issuer(IssuerProp, Opts, SubjectKey), 182 183 {Algo, Parameters} = sign_algorithm(IssuerKey, Opts), 184 185 SignAlgo = #'SignatureAlgorithm'{algorithm = Algo, 186 parameters = Parameters}, 187 Subject = case IssuerProp of 188 true -> %% Is a Root Ca 189 Issuer; 190 _ -> 191 subject(proplists:get_value(subject, Opts),false) 192 end, 193 Rnd = rand:uniform( 1000000000000 ), 194 %% 1 =< Rnd < 1000000000001 195 {#'OTPTBSCertificate'{serialNumber = Rnd, 196 signature = SignAlgo, 197 issuer = Issuer, 198 validity = validity(Opts), 199 subject = Subject, 200 subjectPublicKeyInfo = publickey(SubjectKey), 201 version = Version, 202 extensions = extensions(Opts) 203 }, IssuerKey}. 204 205issuer(true, Opts, SubjectKey) -> 206 %% Self signed 207 {subject(proplists:get_value(subject, Opts), true), SubjectKey}; 208issuer({Issuer, IssuerKey}, _Opts, _SubjectKey) when is_binary(Issuer) -> 209 {issuer_der(Issuer), decode_key(IssuerKey)}; 210issuer({File, IssuerKey}, _Opts, _SubjectKey) when is_list(File) -> 211 {ok, [{cert, Cert, _}|_]} = pem_to_der(File), 212 {issuer_der(Cert), decode_key(IssuerKey)}. 213 214issuer_der(Issuer) -> 215 Decoded = public_key:pkix_decode_cert(Issuer, otp), 216 #'OTPCertificate'{tbsCertificate=Tbs} = Decoded, 217 #'OTPTBSCertificate'{subject=Subject} = Tbs, 218 Subject. 219 220subject(undefined, IsRootCA) -> 221 User = if IsRootCA -> "RootCA"; true -> os:getenv("USER", "test_user") end, 222 Opts = [{email, User ++ "@erlang.org"}, 223 {name, User}, 224 {city, "Stockholm"}, 225 {country, "SE"}, 226 {org, "erlang"}, 227 {org_unit, "testing dep"}], 228 subject(Opts); 229subject(Opts, _) -> 230 subject(Opts). 231 232subject(SubjectOpts) when is_list(SubjectOpts) -> 233 Encode = fun(Opt) -> 234 {Type,Value} = subject_enc(Opt), 235 [#'AttributeTypeAndValue'{type=Type, value=Value}] 236 end, 237 {rdnSequence, [Encode(Opt) || Opt <- SubjectOpts]}. 238 239%% Fill in the blanks 240subject_enc({name, Name}) -> {?'id-at-commonName', {printableString, Name}}; 241subject_enc({email, Email}) -> {?'id-emailAddress', Email}; 242subject_enc({city, City}) -> {?'id-at-localityName', {printableString, City}}; 243subject_enc({state, State}) -> {?'id-at-stateOrProvinceName', {printableString, State}}; 244subject_enc({org, Org}) -> {?'id-at-organizationName', {printableString, Org}}; 245subject_enc({org_unit, OrgUnit}) -> {?'id-at-organizationalUnitName', {printableString, OrgUnit}}; 246subject_enc({country, Country}) -> {?'id-at-countryName', Country}; 247subject_enc({serial, Serial}) -> {?'id-at-serialNumber', Serial}; 248subject_enc({title, Title}) -> {?'id-at-title', {printableString, Title}}; 249subject_enc({dnQualifer, DnQ}) -> {?'id-at-dnQualifier', DnQ}; 250subject_enc(Other) -> Other. 251 252 253extensions(Opts) -> 254 case proplists:get_value(extensions, Opts, []) of 255 false -> 256 asn1_NOVALUE; 257 Exts -> 258 lists:flatten([extension(Ext) || Ext <- default_extensions(Exts)]) 259 end. 260 261default_extensions(Exts) -> 262 Def = [{key_usage, default}, 263 {subject_altname, undefined}, 264 {issuer_altname, undefined}, 265 {basic_constraints, default}, 266 {name_constraints, undefined}, 267 {policy_constraints, undefined}, 268 {ext_key_usage, undefined}, 269 {inhibit_any, undefined}, 270 {auth_key_id, undefined}, 271 {subject_key_id, undefined}, 272 {policy_mapping, undefined}], 273 Filter = fun({Key, _}, D) -> lists:keydelete(Key, 1, D) end, 274 Exts ++ lists:foldl(Filter, Def, Exts). 275 276extension({_, undefined}) -> []; 277 278extension({basic_constraints, Data}) -> 279 case Data of 280 default -> 281 #'Extension'{extnID = ?'id-ce-basicConstraints', 282 extnValue = #'BasicConstraints'{cA=true}, 283 critical=true}; 284 false -> 285 []; 286 Len when is_integer(Len) -> 287 #'Extension'{extnID = ?'id-ce-basicConstraints', 288 extnValue = #'BasicConstraints'{cA=true, pathLenConstraint=Len}, 289 critical=true}; 290 _ -> 291 #'Extension'{extnID = ?'id-ce-basicConstraints', 292 extnValue = Data} 293 end; 294 295extension({key_usage, default}) -> 296 #'Extension'{extnID = ?'id-ce-keyUsage', 297 extnValue = [keyCertSign], critical = true}; 298 299extension({Id, Data, Critical}) -> 300 #'Extension'{extnID = Id, extnValue = Data, critical = Critical}. 301 302 303publickey(#'RSAPrivateKey'{modulus=N, publicExponent=E}) -> 304 Public = #'RSAPublicKey'{modulus=N, publicExponent=E}, 305 Algo = #'PublicKeyAlgorithm'{algorithm= ?rsaEncryption, parameters='NULL'}, 306 #'OTPSubjectPublicKeyInfo'{algorithm = Algo, 307 subjectPublicKey = Public}; 308publickey({#'RSAPrivateKey'{modulus=N, publicExponent=E},#'RSASSA-PSS-params'{}=P}) -> 309 Public = #'RSAPublicKey'{modulus=N, publicExponent=E}, 310 Algo = #'PublicKeyAlgorithm'{algorithm= ?'id-RSASSA-PSS', parameters=P}, 311 #'OTPSubjectPublicKeyInfo'{algorithm = Algo, 312 subjectPublicKey = Public}; 313publickey(#'DSAPrivateKey'{p=P, q=Q, g=G, y=Y}) -> 314 Algo = #'PublicKeyAlgorithm'{algorithm= ?'id-dsa', 315 parameters={params, #'Dss-Parms'{p=P, q=Q, g=G}}}, 316 #'OTPSubjectPublicKeyInfo'{algorithm = Algo, subjectPublicKey = Y}; 317publickey(#'ECPrivateKey'{version = _Version, 318 privateKey = _PrivKey, 319 parameters = Params, 320 publicKey = PubKey}) -> 321 Algo = #'PublicKeyAlgorithm'{algorithm= ?'id-ecPublicKey', parameters=Params}, 322 #'OTPSubjectPublicKeyInfo'{algorithm = Algo, 323 subjectPublicKey = #'ECPoint'{point = PubKey}}. 324 325validity(Opts) -> 326 DefFrom0 = calendar:gregorian_days_to_date(calendar:date_to_gregorian_days(date())-1), 327 DefTo0 = calendar:gregorian_days_to_date(calendar:date_to_gregorian_days(date())+7), 328 {DefFrom, DefTo} = proplists:get_value(validity, Opts, {DefFrom0, DefTo0}), 329 Format = fun({Y,M,D}) -> lists:flatten(io_lib:format("~w~2..0w~2..0w000000Z",[Y,M,D])) end, 330 #'Validity'{notBefore={generalTime, Format(DefFrom)}, 331 notAfter ={generalTime, Format(DefTo)}}. 332 333sign_algorithm(#'RSAPrivateKey'{}, Opts) -> 334 Type = case proplists:get_value(digest, Opts, sha1) of 335 sha1 -> ?'sha1WithRSAEncryption'; 336 sha512 -> ?'sha512WithRSAEncryption'; 337 sha384 -> ?'sha384WithRSAEncryption'; 338 sha256 -> ?'sha256WithRSAEncryption'; 339 md5 -> ?'md5WithRSAEncryption'; 340 md2 -> ?'md2WithRSAEncryption' 341 end, 342 {Type, 'NULL'}; 343sign_algorithm({#'RSAPrivateKey'{},#'RSASSA-PSS-params'{}=P}, Opts) -> 344 {?'id-RSASSA-PSS', P}; 345sign_algorithm(#'DSAPrivateKey'{p=P, q=Q, g=G}, _Opts) -> 346 {?'id-dsa-with-sha1', {params,#'Dss-Parms'{p=P, q=Q, g=G}}}; 347sign_algorithm(#'ECPrivateKey'{parameters = Parms}, Opts) -> 348 Type = case proplists:get_value(digest, Opts, sha1) of 349 sha1 -> ?'ecdsa-with-SHA1'; 350 sha512 -> ?'ecdsa-with-SHA512'; 351 sha384 -> ?'ecdsa-with-SHA384'; 352 sha256 -> ?'ecdsa-with-SHA256' 353 end, 354 {Type, Parms}. 355 356make_key(rsa, _Opts) -> 357 %% (OBS: for testing only) 358 gen_rsa2(64); 359make_key(dsa, _Opts) -> 360 gen_dsa2(128, 20); %% Bytes i.e. {1024, 160} 361make_key(ec, _Opts) -> 362 %% (OBS: for testing only) 363 gen_ec2(secp256k1). 364 365%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% 366%% RSA key generation (OBS: for testing only) 367%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% 368 369gen_rsa2(Size) -> 370 try 371 %% The numbers 2048,17 is choosen to not cause the cryptolib on 372 %% FIPS-enabled test machines be mad at us. 373 public_key:generate_key({rsa, 2048, 17}) 374 catch 375 error:notsup -> 376 %% Disabled dirty_schedulers => crypto:generate_key not working 377 weak_gen_rsa2(Size) 378 end. 379 380 381-define(SMALL_PRIMES, [65537,97,89,83,79,73,71,67,61,59,53, 382 47,43,41,37,31,29,23,19,17,13,11,7,5,3]). 383 384weak_gen_rsa2(Size) -> 385 P = prime(Size), 386 Q = prime(Size), 387 N = P*Q, 388 Tot = (P - 1) * (Q - 1), 389 [E|_] = lists:dropwhile(fun(Candidate) -> (Tot rem Candidate) == 0 end, ?SMALL_PRIMES), 390 {D1,D2} = extended_gcd(E, Tot), 391 D = erlang:max(D1,D2), 392 case D < E of 393 true -> 394 gen_rsa2(Size); 395 false -> 396 {Co1,Co2} = extended_gcd(Q, P), 397 Co = erlang:max(Co1,Co2), 398 #'RSAPrivateKey'{version = 'two-prime', 399 modulus = N, 400 publicExponent = E, 401 privateExponent = D, 402 prime1 = P, 403 prime2 = Q, 404 exponent1 = D rem (P-1), 405 exponent2 = D rem (Q-1), 406 coefficient = Co 407 } 408 end. 409 410%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% 411%% DSA key generation (OBS: for testing only) 412%% See http://en.wikipedia.org/wiki/Digital_Signature_Algorithm 413%% and the fips_186-3.pdf 414%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% 415gen_dsa2(LSize, NSize) -> 416 Q = prime(NSize), %% Choose N-bit prime Q 417 X0 = prime(LSize), 418 P0 = prime((LSize div 2) +1), 419 420 %% Choose L-bit prime modulus P such that p-1 is a multiple of q. 421 case dsa_search(X0 div (2*Q*P0), P0, Q, 1000) of 422 error -> 423 gen_dsa2(LSize, NSize); 424 P -> 425 G = crypto:mod_pow(2, (P-1) div Q, P), % Choose G a number whose multiplicative order modulo p is q. 426 %% such that This may be done by setting g = h^(p-1)/q mod p, commonly h=2 is used. 427 428 X = prime(20), %% Choose x by some random method, where 0 < x < q. 429 Y = crypto:mod_pow(G, X, P), %% Calculate y = g^x mod p. 430 431 #'DSAPrivateKey'{version=0, p = P, q = Q, 432 g = crypto:bytes_to_integer(G), y = crypto:bytes_to_integer(Y), x = X} 433 end. 434 435%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% 436%% EC key generation (OBS: for testing only) 437%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% 438 439gen_ec2(CurveId) -> 440 {PubKey, PrivKey} = crypto:generate_key(ecdh, CurveId), 441 442 #'ECPrivateKey'{version = 1, 443 privateKey = PrivKey, 444 parameters = {namedCurve, pubkey_cert_records:namedCurves(CurveId)}, 445 publicKey = PubKey}. 446 447%% See fips_186-3.pdf 448dsa_search(T, P0, Q, Iter) when Iter > 0 -> 449 P = 2*T*Q*P0 + 1, 450 case is_prime(P, 50) of 451 true -> P; 452 false -> dsa_search(T+1, P0, Q, Iter-1) 453 end; 454dsa_search(_,_,_,_) -> 455 error. 456 457 458%%%%%%% Crypto Math %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% 459prime(ByteSize) -> 460 Rand = odd_rand(ByteSize), 461 prime_odd(Rand, 0). 462 463prime_odd(Rand, N) -> 464 case is_prime(Rand, 50) of 465 true -> 466 Rand; 467 false -> 468 prime_odd(Rand+2, N+1) 469 end. 470 471%% see http://en.wikipedia.org/wiki/Fermat_primality_test 472is_prime(_, 0) -> true; 473is_prime(Candidate, Test) -> 474 CoPrime = odd_rand(10000, Candidate), 475 Result = crypto:mod_pow(CoPrime, Candidate, Candidate) , 476 is_prime(CoPrime, crypto:bytes_to_integer(Result), Candidate, Test). 477 478is_prime(CoPrime, CoPrime, Candidate, Test) -> 479 is_prime(Candidate, Test-1); 480is_prime(_,_,_,_) -> 481 false. 482 483odd_rand(Size) -> 484 Min = 1 bsl (Size*8-1), 485 Max = (1 bsl (Size*8))-1, 486 odd_rand(Min, Max). 487 488odd_rand(Min,Max) -> 489 %% Odd random number N such that Min =< N =< Max 490 Rand = (Min-1) + rand:uniform(Max-Min), % Min =< Rand < Max 491 case Rand rem 2 of 492 0 -> 493 Rand + 1; 494 _ -> 495 Rand 496 end. 497 498extended_gcd(A, B) -> 499 case A rem B of 500 0 -> 501 {0, 1}; 502 N -> 503 {X, Y} = extended_gcd(B, N), 504 {Y, X-Y*(A div B)} 505 end. 506 507pem_to_der(File) -> 508 {ok, PemBin} = file:read_file(File), 509 public_key:pem_decode(PemBin). 510 511der_to_pem(File, Entries) -> 512 PemBin = public_key:pem_encode(Entries), 513 file:write_file(File, PemBin). 514 515