%%
%% %CopyrightBegin%
%%
%% Copyright Ericsson AB 2004-2011. All Rights Reserved.
%%
%% The contents of this file are subject to the Erlang Public License,
%% Version 1.1, (the "License"); you may not use this file except in
%% compliance with the License. You should have received a copy of the
%% Erlang Public License along with this software. If not, it can be
%% retrieved online at http://www.erlang.org/.
%%
%% Software distributed under the License is distributed on an "AS IS"
%% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See
%% the License for the specific language governing rights and limitations
%% under the License.
%%
%% %CopyrightEnd%
%%
%%
%%----------------------------------------------------------------------
-module(ssh_test_lib).
%% Note: This directive should only be used in test suites.
-compile(export_all).
-include_lib("public_key/include/public_key.hrl").
-include("test_server.hrl").
-include("test_server_line.hrl").
-include("../src/ssh.hrl").
-define(TIMEOUT, 50000).
connect(Options) ->
connect(hostname(), inet_port(), Options).
connect(Port, Options) when is_integer(Port) ->
connect(hostname(), Port, Options);
connect(Host, Options) ->
connect(Host, inet_port(), Options).
connect(Host, Port, Options) ->
case ssh:connect(Host, Port, Options) of
{ok, ConnectionRef} ->
ConnectionRef;
Error ->
Error
end.
daemon(Options) ->
daemon(any, inet_port(), Options).
daemon(Port, Options) when is_integer(Port) ->
daemon(any, Port, Options);
daemon(Host, Options) ->
daemon(Host, inet_port(), Options).
daemon(Host, Port, Options) ->
case ssh:daemon(Host, Port, Options) of
{ok, Pid} ->
{Pid, Host, Port};
Error ->
Error
end.
start_shell(Port, IOServer) ->
spawn_link(?MODULE, init_shell, [Port, IOServer]).
init_shell(Port, IOServer) ->
Host = hostname(),
UserDir = get_user_dir(),
Options = [{user_interaction, false}, {silently_accept_hosts,
true}] ++ UserDir,
group_leader(IOServer, self()),
loop_shell(Host, Port, Options).
loop_shell(Host, Port, Options) ->
ssh:shell(Host, Port, Options).
start_io_server() ->
spawn_link(?MODULE, init_io_server, [self()]).
init_io_server(TestCase) ->
process_flag(trap_exit, true),
loop_io_server(TestCase, []).
loop_io_server(TestCase, Buff0) ->
receive
{input, TestCase, Line} ->
%io:format("~p~n",[{input, TestCase, Line}]),
loop_io_server(TestCase, Buff0 ++ [Line]);
{io_request, From, ReplyAs, Request} ->
%io:format("request -> ~p~n",[Request]),
{ok, Reply, Buff} = io_request(Request, TestCase, From,
ReplyAs, Buff0),
%io:format("reply -> ~p~n",[Reply]),
io_reply(From, ReplyAs, Reply),
loop_io_server(TestCase, Buff);
{'EXIT',_, _} ->
erlang:display('EXIT'),
ok
end.
io_request({put_chars, Chars}, TestCase, _, _, Buff) ->
reply(TestCase, Chars),
{ok, ok, Buff};
io_request({put_chars, Enc, Chars}, TestCase, _, _, Buff) ->
reply(TestCase, unicode:characters_to_binary(Chars,Enc,latin1)),
{ok, ok, Buff};
io_request({get_line, _} = Request, _, From, ReplyAs, [] = Buff) ->
erlang:send_after(1000, self(), {io_request, From, ReplyAs, Request}),
{ok, [], Buff};
io_request({get_line, _Enc, _Prompt} = Request, _, From, ReplyAs, [] = Buff) ->
erlang:send_after(1000, self(), {io_request, From, ReplyAs, Request}),
{ok, [], Buff};
io_request({get_line, _Enc,_}, _, _, _, [Line | Buff]) ->
{ok, Line, Buff}.
io_reply(_, _, []) ->
ok;
io_reply(From, ReplyAs, Reply) ->
From ! {io_reply, ReplyAs, Reply}.
reply(_, []) ->
ok;
reply(TestCase, Result) ->
TestCase ! Result.
receive_exec_result(Msg) ->
test_server:format("Expect data! ~p", [Msg]),
receive
Msg ->
test_server:format("1: Collected data ~p", [Msg]),
expected;
Other ->
{unexpected_msg, Other}
end.
receive_exec_end(ConnectionRef, ChannelId) ->
Eof = {ssh_cm, ConnectionRef, {eof, ChannelId}},
ExitStatus = {ssh_cm, ConnectionRef, {exit_status, ChannelId, 0}},
Closed = {ssh_cm, ConnectionRef,{closed, ChannelId}},
case receive_exec_result(ExitStatus) of
{unexpected_msg, Eof} -> %% Open ssh seems to not allways send these messages
%% in the same order!
test_server:format("2: Collected data ~p", [Eof]),
case receive_exec_result(ExitStatus) of
expected ->
expected = receive_exec_result(Closed);
{unexpected_msg, Closed} ->
test_server:format("3: Collected data ~p", [Closed])
end;
expected ->
test_server:format("4: Collected data ~p", [ExitStatus]),
expected = receive_exec_result(Eof),
expected = receive_exec_result(Closed);
Other ->
test_server:fail({unexpected_msg, Other})
end.
receive_exec_result(Data, ConnectionRef, ChannelId) ->
Eof = {ssh_cm, ConnectionRef, {eof, ChannelId}},
Closed = {ssh_cm, ConnectionRef,{closed, ChannelId}},
expected = receive_exec_result(Data),
expected = receive_exec_result(Eof),
expected = receive_exec_result(Closed).
inet_port()->
{ok, Socket} = gen_tcp:listen(0, [{reuseaddr, true}]),
{ok, Port} = inet:port(Socket),
gen_tcp:close(Socket),
Port.
%% copy private keys to given dir from ~/.ssh
get_id_keys(DstDir) ->
SrcDir = filename:join(os:getenv("HOME"), ".ssh"),
RsaOk = copyfile(SrcDir, DstDir, "id_rsa"),
DsaOk = copyfile(SrcDir, DstDir, "id_dsa"),
case {RsaOk, DsaOk} of
{{ok, _}, {ok, _}} -> {ok, both};
{{ok, _}, _} -> {ok, rsa};
{_, {ok, _}} -> {ok, dsa};
{Error, _} -> Error
end.
remove_id_keys(Dir) ->
file:delete(filename:join(Dir, "id_rsa")),
file:delete(filename:join(Dir, "id_dsa")).
copyfile(SrcDir, DstDir, Fn) ->
file:copy(filename:join(SrcDir, Fn),
filename:join(DstDir, Fn)).
failfun(_User, {authmethod,none}) ->
ok;
failfun(User, Reason) ->
error_logger:format("~p failed XXX to login: ~p~n", [User, Reason]).
hostname() ->
{ok,Host} = inet:gethostname(),
Host.
known_hosts(BR) ->
KnownHosts = ssh_file:file_name(user, "known_hosts", []),
B = KnownHosts ++ "xxx",
case BR of
backup ->
file:rename(KnownHosts, B);
restore ->
file:delete(KnownHosts),
file:rename(B, KnownHosts)
end.
save_known_hosts(PrivDir) ->
Src = ssh_file:file_name(user, "known_hosts", []),
Dst = filename:join(PrivDir, "kh_save"),
Ok = file:copy(Src, Dst),
io:format("save ~p -> ~p : ~p", [Src, Dst, Ok]).
restore_known_hosts(_PrivDir) ->
%% Race condition.
ok.
%% Src = filename:join(PrivDir, "kh_save"),
%% Dst = ssh_file:file_name(user, "known_hosts", []),
%% D1 = file:delete(Dst),
%% C = file:copy(Src, Dst),
%% D2 = file:delete(Src),
%% io:format("restore ~p -> ~p : ~p ~p ~p\n", [Src, Dst, D1, C, D2]).
get_user_dir() ->
case os:type() of
{win32, _} ->
[{user_dir, filename:join([os:getenv("HOME"), ".ssh"])}];
_ ->
[]
end.
make_dsa_cert_files(Config) ->
make_dsa_cert_files("", Config).
make_dsa_cert_files(RoleStr, Config) ->
CaInfo = {CaCert, _} = make_cert([{key, dsa}]),
{Cert, CertKey} = make_cert([{key, dsa}, {issuer, CaInfo}]),
CaCertFile = filename:join([?config(data_dir, Config),
RoleStr, "dsa_cacerts.pem"]),
CertFile = filename:join([?config(data_dir, Config),
RoleStr, "dsa_cert.pem"]),
KeyFile = filename:join([?config(data_dir, Config),
RoleStr, "dsa_key.pem"]),
der_to_pem(CaCertFile, [{'Certificate', CaCert, not_encrypted}]),
der_to_pem(CertFile, [{'Certificate', Cert, not_encrypted}]),
der_to_pem(KeyFile, [CertKey]),
{CaCertFile, CertFile, KeyFile}.
make_dsa_public_key_file(P, Q, G, Y, Config) ->
PK = #ssh_key{type = dsa, public = {P,Q,G,Y}},
Enc = ssh_file:encode_public_key(PK),
B64 = ssh_bits:b64_encode(Enc),
FileName = filename:join([?config(data_dir, Config), "ssh_host_dsa_key.pub"]),
file:write_file(FileName, <<"ssh-dss ", B64/binary>>).
%%--------------------------------------------------------------------
%% Create and return a der encoded certificate
%% Option Default
%% -------------------------------------------------------
%% digest sha1
%% validity {date(), date() + week()}
%% version 3
%% subject [] list of the following content
%% {name, Name}
%% {email, Email}
%% {city, City}
%% {state, State}
%% {org, Org}
%% {org_unit, OrgUnit}
%% {country, Country}
%% {serial, Serial}
%% {title, Title}
%% {dnQualifer, DnQ}
%% issuer = {Issuer, IssuerKey} true (i.e. a ca cert is created)
%% (obs IssuerKey migth be {Key, Password}
%% key = KeyFile|KeyBin|rsa|dsa Subject PublicKey rsa or dsa generates key
%%
%%
%% (OBS: The generated keys are for testing only)
%% make_cert([{::atom(), ::term()}]) -> {Cert::binary(), Key::binary()}
%%--------------------------------------------------------------------
make_cert(Opts) ->
SubjectPrivateKey = get_key(Opts),
{TBSCert, IssuerKey} = make_tbs(SubjectPrivateKey, Opts),
Cert = public_key:pkix_sign(TBSCert, IssuerKey),
true = verify_signature(Cert, IssuerKey, undef), %% verify that the keys where ok
{Cert, encode_key(SubjectPrivateKey)}.
%%--------------------------------------------------------------------
%% Writes cert files in Dir with FileName and FileName ++ Suffix
%% write_cert(::string(), ::string(), {Cert,Key}) -> ok
%%--------------------------------------------------------------------
write_cert(Dir, FileName, Suffix, {Cert, Key = {_,_,not_encrypted}}) when is_binary(Cert) ->
ok = der_to_pem(filename:join(Dir, FileName),
[{'Certificate', Cert, not_encrypted}]),
ok = der_to_pem(filename:join(Dir, FileName ++ Suffix), [Key]).
%%--------------------------------------------------------------------
%% Creates a rsa key (OBS: for testing only)
%% the size are in bytes
%% gen_rsa(::integer()) -> {::atom(), ::binary(), ::opaque()}
%%--------------------------------------------------------------------
gen_rsa(Size) when is_integer(Size) ->
Key = gen_rsa2(Size),
{Key, encode_key(Key)}.
%%--------------------------------------------------------------------
%% Creates a dsa key (OBS: for testing only)
%% the sizes are in bytes
%% gen_dsa(::integer()) -> {::atom(), ::binary(), ::opaque()}
%%--------------------------------------------------------------------
gen_dsa(LSize,NSize) when is_integer(LSize), is_integer(NSize) ->
Key = gen_dsa2(LSize, NSize),
{Key, encode_key(Key)}.
%%--------------------------------------------------------------------
%% Verifies cert signatures
%% verify_signature(::binary(), ::tuple()) -> ::boolean()
%%--------------------------------------------------------------------
verify_signature(DerEncodedCert, DerKey, _KeyParams) ->
Key = decode_key(DerKey),
case Key of
#'RSAPrivateKey'{modulus=Mod, publicExponent=Exp} ->
public_key:pkix_verify(DerEncodedCert,
#'RSAPublicKey'{modulus=Mod, publicExponent=Exp});
#'DSAPrivateKey'{p=P, q=Q, g=G, y=Y} ->
public_key:pkix_verify(DerEncodedCert, {Y, #'Dss-Parms'{p=P, q=Q, g=G}})
end.
%%%%%%%%%%%%%%%%%%%%%%%%% Implementation %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
get_key(Opts) ->
case proplists:get_value(key, Opts) of
undefined -> make_key(rsa, Opts);
rsa -> make_key(rsa, Opts);
dsa -> make_key(dsa, Opts);
Key ->
Password = proplists:get_value(password, Opts, no_passwd),
decode_key(Key, Password)
end.
decode_key({Key, Pw}) ->
decode_key(Key, Pw);
decode_key(Key) ->
decode_key(Key, no_passwd).
decode_key(#'RSAPublicKey'{} = Key,_) ->
Key;
decode_key(#'RSAPrivateKey'{} = Key,_) ->
Key;
decode_key(#'DSAPrivateKey'{} = Key,_) ->
Key;
decode_key(PemEntry = {_,_,_}, Pw) ->
public_key:pem_entry_decode(PemEntry, Pw);
decode_key(PemBin, Pw) ->
[KeyInfo] = public_key:pem_decode(PemBin),
decode_key(KeyInfo, Pw).
encode_key(Key = #'RSAPrivateKey'{}) ->
{ok, Der} = 'OTP-PUB-KEY':encode('RSAPrivateKey', Key),
{'RSAPrivateKey', list_to_binary(Der), not_encrypted};
encode_key(Key = #'DSAPrivateKey'{}) ->
{ok, Der} = 'OTP-PUB-KEY':encode('DSAPrivateKey', Key),
{'DSAPrivateKey', list_to_binary(Der), not_encrypted}.
make_tbs(SubjectKey, Opts) ->
Version = list_to_atom("v"++integer_to_list(proplists:get_value(version, Opts, 3))),
IssuerProp = proplists:get_value(issuer, Opts, true),
{Issuer, IssuerKey} = issuer(IssuerProp, Opts, SubjectKey),
{Algo, Parameters} = sign_algorithm(IssuerKey, Opts),
SignAlgo = #'SignatureAlgorithm'{algorithm = Algo,
parameters = Parameters},
Subject = case IssuerProp of
true -> %% Is a Root Ca
Issuer;
_ ->
subject(proplists:get_value(subject, Opts),false)
end,
{#'OTPTBSCertificate'{serialNumber = trunc(random:uniform()*100000000)*10000 + 1,
signature = SignAlgo,
issuer = Issuer,
validity = validity(Opts),
subject = Subject,
subjectPublicKeyInfo = publickey(SubjectKey),
version = Version,
extensions = extensions(Opts)
}, IssuerKey}.
issuer(true, Opts, SubjectKey) ->
%% Self signed
{subject(proplists:get_value(subject, Opts), true), SubjectKey};
issuer({Issuer, IssuerKey}, _Opts, _SubjectKey) when is_binary(Issuer) ->
{issuer_der(Issuer), decode_key(IssuerKey)};
issuer({File, IssuerKey}, _Opts, _SubjectKey) when is_list(File) ->
{ok, [{cert, Cert, _}|_]} = pem_to_der(File),
{issuer_der(Cert), decode_key(IssuerKey)}.
issuer_der(Issuer) ->
Decoded = public_key:pkix_decode_cert(Issuer, otp),
#'OTPCertificate'{tbsCertificate=Tbs} = Decoded,
#'OTPTBSCertificate'{subject=Subject} = Tbs,
Subject.
subject(undefined, IsRootCA) ->
User = if IsRootCA -> "RootCA"; true -> os:getenv("USER") end,
Opts = [{email, User ++ "@erlang.org"},
{name, User},
{city, "Stockholm"},
{country, "SE"},
{org, "erlang"},
{org_unit, "testing dep"}],
subject(Opts);
subject(Opts, _) ->
subject(Opts).
subject(SubjectOpts) when is_list(SubjectOpts) ->
Encode = fun(Opt) ->
{Type,Value} = subject_enc(Opt),
[#'AttributeTypeAndValue'{type=Type, value=Value}]
end,
{rdnSequence, [Encode(Opt) || Opt <- SubjectOpts]}.
%% Fill in the blanks
subject_enc({name, Name}) -> {?'id-at-commonName', {printableString, Name}};
subject_enc({email, Email}) -> {?'id-emailAddress', Email};
subject_enc({city, City}) -> {?'id-at-localityName', {printableString, City}};
subject_enc({state, State}) -> {?'id-at-stateOrProvinceName', {printableString, State}};
subject_enc({org, Org}) -> {?'id-at-organizationName', {printableString, Org}};
subject_enc({org_unit, OrgUnit}) -> {?'id-at-organizationalUnitName', {printableString, OrgUnit}};
subject_enc({country, Country}) -> {?'id-at-countryName', Country};
subject_enc({serial, Serial}) -> {?'id-at-serialNumber', Serial};
subject_enc({title, Title}) -> {?'id-at-title', {printableString, Title}};
subject_enc({dnQualifer, DnQ}) -> {?'id-at-dnQualifier', DnQ};
subject_enc(Other) -> Other.
extensions(Opts) ->
case proplists:get_value(extensions, Opts, []) of
false ->
asn1_NOVALUE;
Exts ->
lists:flatten([extension(Ext) || Ext <- default_extensions(Exts)])
end.
default_extensions(Exts) ->
Def = [{key_usage,undefined},
{subject_altname, undefined},
{issuer_altname, undefined},
{basic_constraints, default},
{name_constraints, undefined},
{policy_constraints, undefined},
{ext_key_usage, undefined},
{inhibit_any, undefined},
{auth_key_id, undefined},
{subject_key_id, undefined},
{policy_mapping, undefined}],
Filter = fun({Key, _}, D) -> lists:keydelete(Key, 1, D) end,
Exts ++ lists:foldl(Filter, Def, Exts).
extension({_, undefined}) -> [];
extension({basic_constraints, Data}) ->
case Data of
default ->
#'Extension'{extnID = ?'id-ce-basicConstraints',
extnValue = #'BasicConstraints'{cA=true},
critical=true};
false ->
[];
Len when is_integer(Len) ->
#'Extension'{extnID = ?'id-ce-basicConstraints',
extnValue = #'BasicConstraints'{cA=true, pathLenConstraint=Len},
critical=true};
_ ->
#'Extension'{extnID = ?'id-ce-basicConstraints',
extnValue = Data}
end;
extension({Id, Data, Critical}) ->
#'Extension'{extnID = Id, extnValue = Data, critical = Critical}.
publickey(#'RSAPrivateKey'{modulus=N, publicExponent=E}) ->
Public = #'RSAPublicKey'{modulus=N, publicExponent=E},
Algo = #'PublicKeyAlgorithm'{algorithm= ?rsaEncryption, parameters='NULL'},
#'OTPSubjectPublicKeyInfo'{algorithm = Algo,
subjectPublicKey = Public};
publickey(#'DSAPrivateKey'{p=P, q=Q, g=G, y=Y}) ->
Algo = #'PublicKeyAlgorithm'{algorithm= ?'id-dsa',
parameters={params, #'Dss-Parms'{p=P, q=Q, g=G}}},
#'OTPSubjectPublicKeyInfo'{algorithm = Algo, subjectPublicKey = Y}.
validity(Opts) ->
DefFrom0 = calendar:gregorian_days_to_date(calendar:date_to_gregorian_days(date())-1),
DefTo0 = calendar:gregorian_days_to_date(calendar:date_to_gregorian_days(date())+7),
{DefFrom, DefTo} = proplists:get_value(validity, Opts, {DefFrom0, DefTo0}),
Format = fun({Y,M,D}) -> lists:flatten(io_lib:format("~w~2..0w~2..0w000000Z",[Y,M,D])) end,
#'Validity'{notBefore={generalTime, Format(DefFrom)},
notAfter ={generalTime, Format(DefTo)}}.
sign_algorithm(#'RSAPrivateKey'{}, Opts) ->
Type = case proplists:get_value(digest, Opts, sha1) of
sha1 -> ?'sha1WithRSAEncryption';
sha512 -> ?'sha512WithRSAEncryption';
sha384 -> ?'sha384WithRSAEncryption';
sha256 -> ?'sha256WithRSAEncryption';
md5 -> ?'md5WithRSAEncryption';
md2 -> ?'md2WithRSAEncryption'
end,
{Type, 'NULL'};
sign_algorithm(#'DSAPrivateKey'{p=P, q=Q, g=G}, _Opts) ->
{?'id-dsa-with-sha1', {params,#'Dss-Parms'{p=P, q=Q, g=G}}}.
make_key(rsa, _Opts) ->
%% (OBS: for testing only)
gen_rsa2(64);
make_key(dsa, _Opts) ->
gen_dsa2(128, 20). %% Bytes i.e. {1024, 160}
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%% RSA key generation (OBS: for testing only)
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
-define(SMALL_PRIMES, [65537,97,89,83,79,73,71,67,61,59,53,
47,43,41,37,31,29,23,19,17,13,11,7,5,3]).
gen_rsa2(Size) ->
P = prime(Size),
Q = prime(Size),
N = P*Q,
Tot = (P - 1) * (Q - 1),
[E|_] = lists:dropwhile(fun(Candidate) -> (Tot rem Candidate) == 0 end, ?SMALL_PRIMES),
{D1,D2} = extended_gcd(E, Tot),
D = erlang:max(D1,D2),
case D < E of
true ->
gen_rsa2(Size);
false ->
{Co1,Co2} = extended_gcd(Q, P),
Co = erlang:max(Co1,Co2),
#'RSAPrivateKey'{version = 'two-prime',
modulus = N,
publicExponent = E,
privateExponent = D,
prime1 = P,
prime2 = Q,
exponent1 = D rem (P-1),
exponent2 = D rem (Q-1),
coefficient = Co
}
end.
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%% DSA key generation (OBS: for testing only)
%% See http://en.wikipedia.org/wiki/Digital_Signature_Algorithm
%% and the fips_186-3.pdf
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
gen_dsa2(LSize, NSize) ->
Q = prime(NSize), %% Choose N-bit prime Q
X0 = prime(LSize),
P0 = prime((LSize div 2) +1),
%% Choose L-bit prime modulus P such that p-1 is a multiple of q.
case dsa_search(X0 div (2*Q*P0), P0, Q, 1000) of
error ->
gen_dsa2(LSize, NSize);
P ->
G = crypto:mod_exp(2, (P-1) div Q, P), % Choose G a number whose multiplicative order modulo p is q.
%% such that This may be done by setting g = h^(p-1)/q mod p, commonly h=2 is used.
X = prime(20), %% Choose x by some random method, where 0 < x < q.
Y = crypto:mod_exp(G, X, P), %% Calculate y = g^x mod p.
#'DSAPrivateKey'{version=0, p=P, q=Q, g=G, y=Y, x=X}
end.
%% See fips_186-3.pdf
dsa_search(T, P0, Q, Iter) when Iter > 0 ->
P = 2*T*Q*P0 + 1,
case is_prime(crypto:mpint(P), 50) of
true -> P;
false -> dsa_search(T+1, P0, Q, Iter-1)
end;
dsa_search(_,_,_,_) ->
error.
%%%%%%% Crypto Math %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
prime(ByteSize) ->
Rand = odd_rand(ByteSize),
crypto:erlint(prime_odd(Rand, 0)).
prime_odd(Rand, N) ->
case is_prime(Rand, 50) of
true ->
Rand;
false ->
NotPrime = crypto:erlint(Rand),
prime_odd(crypto:mpint(NotPrime+2), N+1)
end.
%% see http://en.wikipedia.org/wiki/Fermat_primality_test
is_prime(_, 0) -> true;
is_prime(Candidate, Test) ->
CoPrime = odd_rand(<<0,0,0,4, 10000:32>>, Candidate),
case crypto:mod_exp(CoPrime, Candidate, Candidate) of
CoPrime -> is_prime(Candidate, Test-1);
_ -> false
end.
odd_rand(Size) ->
Min = 1 bsl (Size*8-1),
Max = (1 bsl (Size*8))-1,
odd_rand(crypto:mpint(Min), crypto:mpint(Max)).
odd_rand(Min,Max) ->
Rand = <<Sz:32, _/binary>> = crypto:rand_uniform(Min,Max),
BitSkip = (Sz+4)*8-1,
case Rand of
Odd = <<_:BitSkip, 1:1>> -> Odd;
Even = <<_:BitSkip, 0:1>> ->
crypto:mpint(crypto:erlint(Even)+1)
end.
extended_gcd(A, B) ->
case A rem B of
0 ->
{0, 1};
N ->
{X, Y} = extended_gcd(B, N),
{Y, X-Y*(A div B)}
end.
pem_to_der(File) ->
{ok, PemBin} = file:read_file(File),
public_key:pem_decode(PemBin).
der_to_pem(File, Entries) ->
PemBin = public_key:pem_encode(Entries),
file:write_file(File, PemBin).