aboutsummaryrefslogtreecommitdiffstats
path: root/lib/eldap
diff options
context:
space:
mode:
Diffstat (limited to 'lib/eldap')
-rw-r--r--lib/eldap/doc/src/eldap.xml20
-rw-r--r--lib/eldap/doc/src/notes.xml29
-rw-r--r--lib/eldap/src/eldap.erl5
-rw-r--r--lib/eldap/test/Makefile5
-rw-r--r--lib/eldap/test/eldap_basic_SUITE.erl1130
-rw-r--r--lib/eldap/test/eldap_basic_SUITE_data/RANDbin0 -> 512 bytes
-rw-r--r--lib/eldap/test/eldap_connections_SUITE.erl147
-rw-r--r--lib/eldap/test/eldap_misc_SUITE.erl51
-rw-r--r--lib/eldap/test/make_certs.erl357
9 files changed, 1168 insertions, 576 deletions
diff --git a/lib/eldap/doc/src/eldap.xml b/lib/eldap/doc/src/eldap.xml
index 4417551aa8..c4b1ac36ca 100644
--- a/lib/eldap/doc/src/eldap.xml
+++ b/lib/eldap/doc/src/eldap.xml
@@ -107,19 +107,23 @@ filter() See present/1, substrings/2,
</type>
<desc>
<p>Upgrade the connection associated with <c>Handle</c> to a tls connection if possible.</p>
- <p>The upgrade is done in two phases: first the server is asked for permission to upgrade. Second, if the request is acknowledged, the upgrade is performed.</p>
- <p>Error responese from phase one will not affect the current encryption state of the connection. Those responses are:</p>
+ <p>The upgrade is done in two phases: first the server is asked for permission to upgrade. Second, if the request is acknowledged, the upgrade to tls is performed.</p>
+ <p>Error responses from phase one will not affect the current encryption state of the connection. Those responses are:</p>
<taglist>
<tag><c>tls_already_started</c></tag>
<item>The connection is already encrypted. The connection is not affected.</item>
<tag><c>{response,ResponseFromServer}</c></tag>
<item>The upgrade was refused by the LDAP server. The <c>ResponseFromServer</c> is an atom delivered byt the LDAP server explained in section 2.3 of rfc 2830. The connection is not affected, so it is still un-encrypted.</item>
</taglist>
- <p>Errors in the seconde phase will however end the connection:</p>
+ <p>Errors in the second phase will however end the connection:</p>
<taglist>
<tag><c>Error</c></tag>
<item>Any error responded from ssl:connect/3</item>
</taglist>
+ <p>The <c>Timeout</c> parameter is for the actual tls upgrade (phase 2) while the timeout in
+ <seealso marker="#open/2">erl_tar:open/2</seealso> is used for the initial negotiation about
+ upgrade (phase 1).
+ </p>
</desc>
</func>
<func>
@@ -224,9 +228,9 @@ filter() See present/1, substrings/2,
</type>
<desc>
<p> Modify the DN of an entry. <c>DeleteOldRDN</c> indicates
- whether the current RDN should be removed after operation.
- <c>NewSupDN</c> should be "" if the RDN should not be moved or the new parent which
- the RDN will be moved to.</p>
+ whether the current RDN should be removed from the attribute list after the after operation.
+ <c>NewSupDN</c> is the new parent that the RDN shall be moved to. If the old parent should
+ remain as parent, <c>NewSupDN</c> shall be "".</p>
<pre>
modify_dn(Handle, "cn=Bill Valentine, ou=people, o=Example Org, dc=example, dc=com ",
"cn=Bill Jr Valentine", true, "")
@@ -253,6 +257,10 @@ filter() See present/1, substrings/2,
Filter = eldap:substrings("cn", [{any,"V"}]),
search(Handle, [{base, "dc=example, dc=com"}, {filter, Filter}, {attributes, ["cn"]}]),
</pre>
+ <p>The <c>timeout</c> option in the <c>SearchOptions</c> is for the ldap server, while
+ the timeout in <seealso marker="#open/2">erl_tar:open/2</seealso> is used for each
+ individual request in the search operation.
+ </p>
</desc>
</func>
diff --git a/lib/eldap/doc/src/notes.xml b/lib/eldap/doc/src/notes.xml
index f92d100757..e5cbcb26ff 100644
--- a/lib/eldap/doc/src/notes.xml
+++ b/lib/eldap/doc/src/notes.xml
@@ -30,6 +30,35 @@
</header>
<p>This document describes the changes made to the Eldap application.</p>
+<section><title>Eldap 1.1</title>
+
+ <section><title>Fixed Bugs and Malfunctions</title>
+ <list>
+ <item>
+ <p>
+ Fixed that eldap:open did not use the Timeout parameter
+ when calling ssl:connect. (Thanks Wiesław Bieniek for
+ reporting)</p>
+ <p>
+ Own Id: OTP-12311</p>
+ </item>
+ </list>
+ </section>
+
+
+ <section><title>Improvements and New Features</title>
+ <list>
+ <item>
+ <p>
+ Added the LDAP filter <c>extensibleMatch</c>.</p>
+ <p>
+ Own Id: OTP-12174</p>
+ </item>
+ </list>
+ </section>
+
+</section>
+
<section><title>Eldap 1.0.4</title>
<section><title>Fixed Bugs and Malfunctions</title>
diff --git a/lib/eldap/src/eldap.erl b/lib/eldap/src/eldap.erl
index 9f7aca287b..80718bc106 100644
--- a/lib/eldap/src/eldap.erl
+++ b/lib/eldap/src/eldap.erl
@@ -107,7 +107,8 @@ getopts(Handle, OptNames) when is_pid(Handle), is_list(OptNames) ->
%%% --------------------------------------------------------------------
close(Handle) when is_pid(Handle) ->
- send(Handle, close).
+ send(Handle, close),
+ ok.
%%% --------------------------------------------------------------------
%%% Set who we should link ourselves to
@@ -394,7 +395,7 @@ parse_args([{port, Port}|T], Cpid, Data) when is_integer(Port) ->
parse_args([{timeout, Timeout}|T], Cpid, Data) when is_integer(Timeout),Timeout>0 ->
parse_args(T, Cpid, Data#eldap{timeout = Timeout});
parse_args([{anon_auth, true}|T], Cpid, Data) ->
- parse_args(T, Cpid, Data#eldap{anon_auth = false});
+ parse_args(T, Cpid, Data#eldap{anon_auth = true});
parse_args([{anon_auth, _}|T], Cpid, Data) ->
parse_args(T, Cpid, Data);
parse_args([{ssl, true}|T], Cpid, Data) ->
diff --git a/lib/eldap/test/Makefile b/lib/eldap/test/Makefile
index 24e71cebaa..28a7a107e1 100644
--- a/lib/eldap/test/Makefile
+++ b/lib/eldap/test/Makefile
@@ -28,8 +28,9 @@ INCLUDES= -I. -I ../include
# ----------------------------------------------------
MODULES= \
- eldap_connections_SUITE \
- eldap_basic_SUITE
+ eldap_basic_SUITE \
+ make_certs
+
ERL_FILES= $(MODULES:%=%.erl)
diff --git a/lib/eldap/test/eldap_basic_SUITE.erl b/lib/eldap/test/eldap_basic_SUITE.erl
index d87f3ac4ac..137c61b2d9 100644
--- a/lib/eldap/test/eldap_basic_SUITE.erl
+++ b/lib/eldap/test/eldap_basic_SUITE.erl
@@ -1,7 +1,7 @@
%%
%% %CopyrightBegin%
%%
-%% Copyright Ericsson AB 2012-2013. All Rights Reserved.
+%% Copyright Ericsson AB 2012-2014. 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
@@ -24,305 +24,919 @@
%%-include_lib("common_test/include/ct.hrl").
-include_lib("test_server/include/test_server.hrl").
-include_lib("eldap/include/eldap.hrl").
+-include_lib("eldap/ebin/ELDAPv3.hrl").
+
-define(TIMEOUT, 120000). % 2 min
+all() ->
+ [app,
+ appup,
+ {group, encode_decode},
+ {group, return_values},
+ {group, v4_connections},
+ {group, v6_connections},
+ {group, plain_api},
+ {group, ssl_api},
+ {group, start_tls_api}
+ ].
+
+groups() ->
+ [{encode_decode, [], [encode,
+ decode
+ ]},
+ {plain_api, [], [{group,api}]},
+ {ssl_api, [], [{group,api}, start_tls_on_ssl_should_fail]},
+ {start_tls_api, [], [{group,api}, start_tls_twice_should_fail]},
+
+ {api, [], [{group,api_not_bound},
+ {group,api_bound}]},
+
+ {api_not_bound, [], [elementary_search, search_non_existant,
+ add_when_not_bound,
+ bind]},
+ {api_bound, [], [add_when_bound,
+ add_already_exists,
+ more_add,
+ search_filter_equalityMatch,
+ search_filter_substring_any,
+ search_filter_initial,
+ search_filter_final,
+ search_filter_and,
+ search_filter_or,
+ search_filter_and_not,
+ search_two_hits,
+ modify,
+ delete,
+ modify_dn_delete_old,
+ modify_dn_keep_old]},
+ {v4_connections, [], connection_tests()},
+ {v6_connections, [], connection_tests()},
+ {return_values, [], [open_ret_val_success,
+ open_ret_val_error,
+ close_ret_val]}
+ ].
+
+connection_tests() ->
+ [tcp_connection,
+ tcp_connection_option,
+ ssl_connection,
+ client_side_start_tls_timeout,
+ client_side_bind_timeout,
+ client_side_add_timeout,
+ client_side_search_timeout
+ ].
+
+
+
init_per_suite(Config) ->
- StartSsl = try ssl:start()
- catch
- Error:Reason ->
- {skip, lists:flatten(io_lib:format("eldap init_per_suite failed to start ssl Error=~p Reason=~p", [Error, Reason]))}
- end,
- case StartSsl of
- ok ->
- chk_config(ldap_server, {"localhost",9876},
- chk_config(ldaps_server, {"localhost",9877},
- Config));
- _ ->
- StartSsl
- end.
+ SSL_available = init_ssl_certs_et_al(Config),
+ LDAP_server = find_first_server(false, [{config,eldap_server}, {config,ldap_server}, {"localhost",9876}]),
+ LDAPS_server =
+ case SSL_available of
+ true ->
+ find_first_server(true, [{config,ldaps_server}, {"localhost",9877}]);
+ false ->
+ undefined
+ end,
+ [{ssl_available, SSL_available},
+ {ldap_server, LDAP_server},
+ {ldaps_server, LDAPS_server} | Config].
end_per_suite(_Config) ->
- ok.
-
-init_per_testcase(_TestCase, Config0) ->
- {EldapHost,Port} = proplists:get_value(ldap_server,Config0),
- try
- {ok, Handle} = eldap:open([EldapHost], [{port,Port}]),
- ok = eldap:simple_bind(Handle, "cn=Manager,dc=ericsson,dc=se", "hejsan"),
- {ok, MyHost} = inet:gethostname(),
- Path = "dc="++MyHost++",dc=ericsson,dc=se",
- eldap:add(Handle,"dc=ericsson,dc=se",
- [{"objectclass", ["dcObject", "organization"]},
- {"dc", ["ericsson"]}, {"o", ["Testing"]}]),
- eldap:add(Handle,Path,
- [{"objectclass", ["dcObject", "organization"]},
- {"dc", [MyHost]}, {"o", ["Test machine"]}]),
- [{eldap_path,Path}|Config0]
- catch error:{badmatch,Error} ->
- io:format("Eldap init error ~p~n ~p~n",[Error, erlang:get_stacktrace()]),
- {skip, lists:flatten(io_lib:format("Ldap init failed with host ~p:~p. Error=~p", [EldapHost,Port,Error]))}
+ ssl:stop().
+
+
+init_per_group(return_values, Config) ->
+ case ?config(ldap_server,Config) of
+ undefined ->
+ {skip, "LDAP server not availble"};
+ {Host,Port} ->
+ ct:comment("ldap://~s:~p",[Host,Port]),
+ Config
+ end;
+init_per_group(plain_api, Config0) ->
+ case ?config(ldap_server,Config0) of
+ undefined ->
+ {skip, "LDAP server not availble"};
+ Server = {Host,Port} ->
+ ct:comment("ldap://~s:~p",[Host,Port]),
+ initialize_db([{server,Server}, {ssl_flag,false}, {start_tls,false} | Config0])
+ end;
+init_per_group(ssl_api, Config0) ->
+ case ?config(ldaps_server,Config0) of
+ undefined ->
+ {skip, "LDAPS server not availble"};
+ Server = {Host,Port} ->
+ ct:comment("ldaps://~s:~p",[Host,Port]),
+ initialize_db([{server,Server}, {ssl_flag,true}, {start_tls,false} | Config0])
+ end;
+init_per_group(start_tls_api, Config0) ->
+ case {?config(ldap_server,Config0), ?config(ssl_available,Config0)} of
+ {undefined,true} ->
+ {skip, "LDAP server not availble"};
+ {_,false} ->
+ {skip, "TLS not availble"};
+ {Server={Host,Port}, true} ->
+ ct:comment("ldap://~s:~p + start_tls",[Host,Port]),
+ Config = [{server,Server}, {ssl_flag,false} | Config0],
+ case supported_extension("1.3.6.1.4.1.1466.20037", Config) of
+ true -> initialize_db([{start_tls,true} | Config]);
+ false -> {skip, "start_tls not supported according to the server"}
+ end
+ end;
+init_per_group(v4_connections, Config) ->
+ [{tcp_listen_opts, [{reuseaddr, true}]},
+ {listen_host, "localhost"},
+ {tcp_connect_opts, []}
+ | Config];
+init_per_group(v6_connections, Config) ->
+ {ok, Hostname} = inet:gethostname(),
+ case lists:member(list_to_atom(Hostname), ct:get_config(ipv6_hosts,[])) of
+ true ->
+ [{tcp_listen_opts, [inet6,{reuseaddr, true}]},
+ {listen_host, "::"},
+ {tcp_connect_opts, [{tcpopts,[inet6]}]}
+ | Config];
+ false ->
+ {skip, io_lib:format("~p is not an ipv6_host",[Hostname])}
+ end;
+init_per_group(_, Config) ->
+ Config.
+
+end_per_group(plain_api, Config) -> clear_db(Config);
+end_per_group(ssl_api, Config) -> clear_db(Config);
+end_per_group(start_tls_api, Config) -> clear_db(Config);
+end_per_group(_Group, Config) -> Config.
+
+
+init_per_testcase(ssl_connection, Config) ->
+ case ?config(ssl_available,Config) of
+ true ->
+ SSL_Port = 9999,
+ CertFile = filename:join(?config(data_dir,Config), "certs/server/cert.pem"),
+ KeyFile = filename:join(?config(data_dir,Config), "certs/server/key.pem"),
+
+ Parent = self(),
+ Listener = spawn_link(
+ fun() ->
+ case ssl:listen(SSL_Port, [{certfile, CertFile},
+ {keyfile, KeyFile}
+ | ?config(tcp_listen_opts,Config)
+ ]) of
+ {ok,SSL_LSock} ->
+ Parent ! {ok,self()},
+ (fun L() ->
+ ct:log("ssl server waiting for connections...",[]),
+ {ok, S} = ssl:transport_accept(SSL_LSock),
+ ct:log("ssl:transport_accept/1 ok",[]),
+ ok = ssl:ssl_accept(S),
+ ct:log("ssl:ssl_accept/1 ok",[]),
+ L()
+ end)();
+ Other ->
+ Parent ! {not_ok,Other,self()}
+ end
+ end),
+ receive
+ {ok,Listener} ->
+ ct:log("SSL listening to port ~p (process ~p)",[SSL_Port, Listener]),
+ [{ssl_listener,Listener},
+ {ssl_listen_port,SSL_Port},
+ {ssl_connect_opts,[]}
+ | Config];
+ {no_ok,SSL_Other,Listener} ->
+ ct:log("ssl:listen on port ~p failed: ~p",[SSL_Port,SSL_Other]),
+ {fail, "ssl:listen/2 failed"}
+ after 5000 ->
+ {fail, "Waiting for ssl:listen timeout"}
+ end;
+ false ->
+ {skip, "ssl not available"}
+ end;
+
+init_per_testcase(TC, Config) ->
+ case lists:member(TC,connection_tests()) of
+ true ->
+ case gen_tcp:listen(0, proplists:get_value(tcp_listen_opts,Config)) of
+ {ok,LSock} ->
+ {ok,{_,Port}} = inet:sockname(LSock),
+ [{listen_socket,LSock},
+ {listen_port,Port}
+ | Config];
+ Other ->
+ {fail, Other}
+ end;
+
+ false ->
+ case proplists:get_value(name,?config(tc_group_properties, Config)) of
+ api_not_bound ->
+ {ok,H} = open(Config),
+ [{handle,H} | Config];
+ api_bound ->
+ {ok,H} = open(Config),
+ ok = eldap:simple_bind(H,
+ "cn=Manager,dc=ericsson,dc=se",
+ "hejsan"),
+ [{handle,H} | Config];
+ _Name ->
+ Config
+ end
end.
-end_per_testcase(_TestCase, Config) ->
- {EHost, Port} = proplists:get_value(ldap_server, Config),
- Path = proplists:get_value(eldap_path, Config),
- {ok, H} = eldap:open([EHost], [{port, Port}]),
- ok = eldap:simple_bind(H, "cn=Manager,dc=ericsson,dc=se", "hejsan"),
- case eldap:search(H, [{base, Path},
- {filter, eldap:present("objectclass")},
- {scope, eldap:wholeSubtree()}])
- of
- {ok, {eldap_search_result, Entries, _}} ->
- [ok = eldap:delete(H, Entry) || {eldap_entry, Entry, _} <- Entries];
- _ -> ignore
- end,
+end_per_testcase(_, Config) ->
+ catch gen_tcp:close( proplists:get_value(listen_socket, Config) ),
+ catch eldap:close( proplists:get_value(handle,Config) ).
- ok.
-%% suite() ->
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+%%%
+%%% Test cases
+%%%
-all() ->
- [app,
- appup,
- api,
- ssl_api,
- start_tls,
- tls_operations,
- start_tls_twice,
- start_tls_on_ssl
- ].
-
-app(doc) -> "Test that the eldap app file is ok";
-app(suite) -> [];
+%%%----------------------------------------------------------------
+%%% Test that the eldap app file is ok
app(Config) when is_list(Config) ->
ok = test_server:app_test(eldap).
-%% Test that the eldap appup file is ok
+%%%----------------------------------------------------------------
+%%% Test that the eldap appup file is ok
appup(Config) when is_list(Config) ->
ok = test_server:appup_test(eldap).
-api(doc) -> "Basic test that all api functions works as expected";
-api(suite) -> [];
-api(Config) ->
- {Host,Port} = proplists:get_value(ldap_server, Config),
- {ok, H} = eldap:open([Host], [{port,Port}
- ,{log,fun(Lvl,Fmt,Args)-> io:format("~p: ~s",[Lvl,io_lib:format(Fmt,Args)]) end}
- ]),
- %% {ok, H} = eldap:open([Host], [{port,Port+1}, {ssl, true}]),
- do_api_checks(H, Config),
- eldap:close(H),
- ok.
+%%%----------------------------------------------------------------
+open_ret_val_success(Config) ->
+ {Host,Port} = ?config(ldap_server,Config),
+ {ok,H} = eldap:open([Host], [{port,Port}]),
+ catch eldap:close(H).
+
+%%%----------------------------------------------------------------
+open_ret_val_error(_Config) ->
+ {error,_} = eldap:open(["nohost.example.com"], [{port,65535}]).
+
+%%%----------------------------------------------------------------
+close_ret_val(Config) ->
+ {Host,Port} = ?config(ldap_server,Config),
+ {ok,H} = eldap:open([Host], [{port,Port}]),
+ ok = eldap:close(H).
+
+%%%----------------------------------------------------------------
+tcp_connection(Config) ->
+ Host = proplists:get_value(listen_host, Config),
+ Port = proplists:get_value(listen_port, Config),
+ Opts = proplists:get_value(tcp_connect_opts, Config),
+ case eldap:open([Host], [{port,Port}|Opts]) of
+ {ok,_H} ->
+ Sl = proplists:get_value(listen_socket, Config),
+ case gen_tcp:accept(Sl,1000) of
+ {ok,_S} -> ok;
+ {error,timeout} -> ct:fail("server side accept timeout",[]);
+ Other -> ct:fail("gen_tdp:accept failed: ~p",[Other])
+ end;
+ Other -> ct:fail("eldap:open failed: ~p",[Other])
+ end.
+%%%----------------------------------------------------------------
+ssl_connection(Config) ->
+ Host = proplists:get_value(listen_host, Config),
+ Port = proplists:get_value(ssl_listen_port, Config),
+ Opts = proplists:get_value(tcp_connect_opts, Config),
+ SSLOpts = proplists:get_value(ssl_connect_opts, Config),
+ case eldap:open([Host], [{port,Port},
+ {ssl,true},
+ {timeout,5000},
+ {sslopts,SSLOpts}|Opts]) of
+ {ok,_H} -> ok;
+ Other -> ct:fail("eldap:open failed: ~p",[Other])
+ end.
-ssl_api(doc) -> "Basic test that all api functions works as expected";
-ssl_api(suite) -> [];
-ssl_api(Config) ->
- {Host,Port} = proplists:get_value(ldaps_server, Config),
- {ok, H} = eldap:open([Host], [{port,Port}, {ssl,true}]),
- do_api_checks(H, Config),
- eldap:close(H),
- ok.
+%%%----------------------------------------------------------------
+client_side_add_timeout(Config) ->
+ client_timeout(
+ fun(H) ->
+ eldap:add(H, "cn=Foo Bar,dc=host,dc=ericsson,dc=se",
+ [{"objectclass", ["person"]},
+ {"cn", ["Foo Bar"]},
+ {"sn", ["Bar"]},
+ {"telephoneNumber", ["555-1232", "555-5432"]}])
+ end, Config).
+
+%%%----------------------------------------------------------------
+client_side_bind_timeout(Config) ->
+ client_timeout(
+ fun(H) ->
+ eldap:simple_bind(H, anon, anon)
+ end, Config).
+
+%%%----------------------------------------------------------------
+client_side_search_timeout(Config) ->
+ client_timeout(
+ fun(H) ->
+ eldap:search(H, [{base,"dc=host,dc=ericsson,dc=se"},
+ {filter, eldap:present("objectclass")},
+ {scope, eldap:wholeSubtree()}])
+ end, Config).
+
+%%%----------------------------------------------------------------
+client_side_start_tls_timeout(Config) ->
+ client_timeout(
+ fun(H) ->
+ eldap:start_tls(H, [])
+ end, Config).
+
+%%%----------------------------------------------------------------
+tcp_connection_option(Config) ->
+ Host = proplists:get_value(listen_host, Config),
+ Port = proplists:get_value(listen_port, Config),
+ Opts = proplists:get_value(tcp_connect_opts, Config),
+ Sl = proplists:get_value(listen_socket, Config),
+
+ %% Make an option value to test. The option must be implemented on all
+ %% platforms that we test on. Must check what the default value is
+ %% so we don't happen to choose that particular value.
+ {ok,[{linger,DefaultLinger}]} = inet:getopts(Sl, [linger]),
+ TestLinger = case DefaultLinger of
+ {false,_} -> {true,5};
+ {true,_} -> {false,0}
+ end,
+
+ case catch eldap:open([Host],
+ [{port,Port},{tcpopts,[{linger,TestLinger}]}|Opts]) of
+ {ok,H} ->
+ case gen_tcp:accept(Sl,1000) of
+ {ok,_} ->
+ case eldap:getopts(H, [{tcpopts,[linger]}]) of
+ {ok,[{tcpopts,[{linger,ActualLinger}]}]} ->
+ case ActualLinger of
+ TestLinger ->
+ ok;
+ DefaultLinger ->
+ ct:fail("eldap:getopts: 'linger' didn't change,"
+ " got ~p (=default) expected ~p",
+ [ActualLinger,TestLinger]);
+ _ ->
+ ct:fail("eldap:getopts: bad 'linger', got ~p expected ~p",
+ [ActualLinger,TestLinger])
+ end;
+ Other ->
+ ct:fail("eldap:getopts: bad result ~p",[Other])
+ end;
+ {error,timeout} ->
+ ct:fail("server side accept timeout",[])
+ end;
+
+ Other ->
+ ct:fail("eldap:open failed: ~p",[Other])
+ end.
-start_tls(doc) -> "Test that an existing (tcp) connection can be upgraded to tls";
-start_tls(suite) -> [];
-start_tls(Config) ->
- {Host,Port} = proplists:get_value(ldap_server, Config),
- {ok, H} = eldap:open([Host], [{port,Port}]),
- ok = eldap:start_tls(H, [
- {keyfile, filename:join([proplists:get_value(data_dir,Config),
- "certs/client/key.pem"])}
- ]),
- eldap:close(H).
+%%%----------------------------------------------------------------
+%%% Basic test that all api functions works as expected
+
+%%%----------------------------------------------------------------
+elementary_search(Config) ->
+ {ok, #eldap_search_result{entries=[_]}} =
+ eldap:search(?config(handle,Config),
+ #eldap_search{base = ?config(eldap_path, Config),
+ filter= eldap:present("objectclass"),
+ scope = eldap:wholeSubtree()}).
+
+%%%----------------------------------------------------------------
+search_non_existant(Config) ->
+ {error, noSuchObject} =
+ eldap:search(?config(handle,Config),
+ #eldap_search{base = "cn=Bar," ++ ?config(eldap_path, Config),
+ filter= eldap:present("objectclass"),
+ scope = eldap:wholeSubtree()}).
+
+%%%----------------------------------------------------------------
+add_when_not_bound(Config) ->
+ {error, _} = eldap:add(?config(handle,Config),
+ "cn=Jonas Jonsson," ++ ?config(eldap_path, Config),
+ [{"objectclass", ["person"]},
+ {"cn", ["Jonas Jonsson"]},
+ {"sn", ["Jonsson"]}]).
+
+%%%----------------------------------------------------------------
+bind(Config) ->
+ ok = eldap:simple_bind(?config(handle,Config),
+ "cn=Manager,dc=ericsson,dc=se",
+ "hejsan").
+
+%%%----------------------------------------------------------------
+add_when_bound(Config) ->
+ ok = eldap:add(?config(handle, Config),
+ "cn=Jonas Jonsson," ++ ?config(eldap_path, Config),
+ [{"objectclass", ["person"]},
+ {"cn", ["Jonas Jonsson"]},
+ {"sn", ["Jonsson"]}]).
+
+%%%----------------------------------------------------------------
+add_already_exists(Config) ->
+ {error, entryAlreadyExists} =
+ eldap:add(?config(handle, Config),
+ "cn=Jonas Jonsson," ++ ?config(eldap_path, Config),
+ [{"objectclass", ["person"]},
+ {"cn", ["Jonas Jonsson"]},
+ {"sn", ["Jonsson"]}]).
+
+%%%----------------------------------------------------------------
+more_add(Config) ->
+ H = ?config(handle, Config),
+ BasePath = ?config(eldap_path, Config),
+ ok = eldap:add(H, "cn=Foo Bar," ++ BasePath,
+ [{"objectclass", ["person"]},
+ {"cn", ["Foo Bar"]},
+ {"sn", ["Bar"]},
+ {"telephoneNumber", ["555-1232", "555-5432"]}]),
+ ok = eldap:add(H, "ou=Team," ++ BasePath,
+ [{"objectclass", ["organizationalUnit"]},
+ {"ou", ["Team"]}]).
-tls_operations(doc) -> "Test that an upgraded connection is usable for ldap stuff";
-tls_operations(suite) -> [];
-tls_operations(Config) ->
- {Host,Port} = proplists:get_value(ldap_server, Config),
- {ok, H} = eldap:open([Host], [{port,Port}]),
- ok = eldap:start_tls(H, [
- {keyfile, filename:join([proplists:get_value(data_dir,Config),
- "certs/client/key.pem"])}
- ]),
- do_api_checks(H, Config),
+%%%----------------------------------------------------------------
+search_filter_equalityMatch(Config) ->
+ BasePath = ?config(eldap_path, Config),
+ ExpectedDN = "cn=Jonas Jonsson," ++ BasePath,
+ {ok, #eldap_search_result{entries=[#eldap_entry{object_name=ExpectedDN}]}} =
+ eldap:search(?config(handle, Config),
+ #eldap_search{base = BasePath,
+ filter = eldap:equalityMatch("sn", "Jonsson"),
+ scope=eldap:singleLevel()}).
+
+%%%----------------------------------------------------------------
+search_filter_substring_any(Config) ->
+ BasePath = ?config(eldap_path, Config),
+ ExpectedDN = "cn=Jonas Jonsson," ++ BasePath,
+ {ok, #eldap_search_result{entries=[#eldap_entry{object_name=ExpectedDN}]}} =
+ eldap:search(?config(handle, Config),
+ #eldap_search{base = BasePath,
+ filter = eldap:substrings("sn", [{any, "ss"}]),
+ scope=eldap:singleLevel()}).
+
+%%%----------------------------------------------------------------
+search_filter_initial(Config) ->
+ H = ?config(handle, Config),
+ BasePath = ?config(eldap_path, Config),
+ ExpectedDN = "cn=Foo Bar," ++ BasePath,
+ {ok, #eldap_search_result{entries=[#eldap_entry{object_name=ExpectedDN}]}} =
+ eldap:search(H,
+ #eldap_search{base = BasePath,
+ filter = eldap:substrings("sn", [{initial, "B"}]),
+ scope=eldap:singleLevel()}).
+
+%%%----------------------------------------------------------------
+search_filter_final(Config) ->
+ H = ?config(handle, Config),
+ BasePath = ?config(eldap_path, Config),
+ ExpectedDN = "cn=Foo Bar," ++ BasePath,
+ {ok, #eldap_search_result{entries=[#eldap_entry{object_name=ExpectedDN}]}} =
+ eldap:search(H,
+ #eldap_search{base = BasePath,
+ filter = eldap:substrings("sn", [{final, "r"}]),
+ scope=eldap:singleLevel()}).
+
+%%%----------------------------------------------------------------
+search_filter_and(Config) ->
+ H = ?config(handle, Config),
+ BasePath = ?config(eldap_path, Config),
+ ExpectedDN = "cn=Foo Bar," ++ BasePath,
+ {ok, #eldap_search_result{entries=[#eldap_entry{object_name=ExpectedDN}]}} =
+ eldap:search(H,
+ #eldap_search{base = BasePath,
+ filter = eldap:'and'([eldap:substrings("sn", [{any, "a"}]),
+ eldap:equalityMatch("cn","Foo Bar")]),
+ scope=eldap:singleLevel()}).
+
+%%%----------------------------------------------------------------
+search_filter_or(Config) ->
+ H = ?config(handle, Config),
+ BasePath = ?config(eldap_path, Config),
+ ExpectedDNs = lists:sort(["cn=Foo Bar," ++ BasePath,
+ "ou=Team," ++ BasePath]),
+ {ok, #eldap_search_result{entries=Es}} =
+ eldap:search(H,
+ #eldap_search{base = BasePath,
+ filter = eldap:'or'([eldap:substrings("sn", [{any, "a"}]),
+ eldap:equalityMatch("ou","Team")]),
+ scope=eldap:singleLevel()}),
+ ExpectedDNs = lists:sort([DN || #eldap_entry{object_name=DN} <- Es]).
+
+%%%----------------------------------------------------------------
+search_filter_and_not(Config) ->
+ H = ?config(handle, Config),
+ BasePath = ?config(eldap_path, Config),
+ {ok, #eldap_search_result{entries=[]}} =
+ eldap:search(H,
+ #eldap_search{base = BasePath,
+ filter = eldap:'and'([eldap:substrings("sn", [{any, "a"}]),
+ eldap:'not'(
+ eldap:equalityMatch("cn","Foo Bar")
+ )]),
+ scope=eldap:singleLevel()}).
+
+%%%----------------------------------------------------------------
+search_two_hits(Config) ->
+ H = ?config(handle, Config),
+ BasePath = ?config(eldap_path, Config),
+ DN1 = "cn=Santa Claus," ++ BasePath,
+ DN2 = "cn=Jultomten," ++ BasePath,
+ %% Add two objects:
+ ok = eldap:add(H, DN1,
+ [{"objectclass", ["person"]},
+ {"cn", ["Santa Claus"]},
+ {"sn", ["Santa"]},
+ {"description", ["USA"]}]),
+ ok = eldap:add(H, DN2,
+ [{"objectclass", ["person"]},
+ {"cn", ["Jultomten"]},
+ {"sn", ["Tomten"]},
+ {"description", ["Sweden"]}]),
+
+ %% Search for them:
+ {ok, #eldap_search_result{entries=Es}} =
+ eldap:search(H,
+ #eldap_search{base = BasePath,
+ filter = eldap:present("description"),
+ scope=eldap:singleLevel()}),
+
+ %% And check that they are the expected ones:
+ ExpectedDNs = lists:sort([DN1, DN2]),
+ ExpectedDNs = lists:sort([D || #eldap_entry{object_name=D} <- Es]),
+
+ %% Restore the database:
+ [ok=eldap:delete(H,DN) || DN <- ExpectedDNs].
+
+%%%----------------------------------------------------------------
+modify(Config) ->
+ H = ?config(handle, Config),
+ BasePath = ?config(eldap_path, Config),
+ %% The object to modify
+ DN = "cn=Foo Bar," ++ BasePath,
+
+ %% Save a copy to restore later:
+ {ok,OriginalAttrs} = attributes(H, DN),
+
+ %% Do a change
+ Mod = [eldap:mod_replace("telephoneNumber", ["555-12345"]),
+ eldap:mod_add("description", ["Nice guy"])],
+ ok = eldap:modify(H, DN, Mod),
+
+ %% Check that the object was changed
+ {ok, #eldap_search_result{entries=[#eldap_entry{object_name=DN}]}} =
+ eldap:search(H,
+ #eldap_search{base = BasePath,
+ filter = eldap:equalityMatch("telephoneNumber", "555-12345"),
+ scope=eldap:singleLevel()}),
+
+ %% Do another type of change
+ ok = eldap:modify(H, DN, [eldap:mod_delete("telephoneNumber", [])]),
+ %% and check that it worked by repeating the test above
+ {ok, #eldap_search_result{entries=[]}} =
+ eldap:search(H,
+ #eldap_search{base = BasePath,
+ filter = eldap:equalityMatch("telephoneNumber", "555-12345"),
+ scope=eldap:singleLevel()}),
+ %% restore the orignal version:
+ restore_original_object(H, DN, OriginalAttrs).
+
+%%%----------------------------------------------------------------
+delete(Config) ->
+ H = ?config(handle, Config),
+ BasePath = ?config(eldap_path, Config),
+ %% The element to play with:
+ DN = "cn=Jonas Jonsson," ++ BasePath,
+
+ %% Prove that the element is present before deletion
+ {ok,OriginalAttrs} = attributes(H, DN),
+
+ %% Do what the test has to do:
+ ok = eldap:delete(H, DN),
+ %% check that it really was deleted:
+ {error, noSuchObject} = eldap:delete(H, DN),
+
+ %% And restore the object for subsequent tests
+ restore_original_object(H, DN, OriginalAttrs).
+
+%%%----------------------------------------------------------------
+modify_dn_delete_old(Config) ->
+ H = ?config(handle, Config),
+ BasePath = ?config(eldap_path, Config),
+ OrigCN = "Foo Bar",
+ OriginalRDN = "cn="++OrigCN,
+ DN = OriginalRDN ++ "," ++ BasePath,
+ NewCN = "Niclas Andre",
+ NewRDN = "cn="++NewCN,
+ NewDN = NewRDN ++ "," ++BasePath,
+
+ %% Check that the object to modify_dn of exists:
+ {ok,OriginalAttrs} = attributes(H, DN),
+ CN_orig = lists:sort(proplists:get_value("cn",OriginalAttrs)),
+ {ok, #eldap_search_result{entries=[#eldap_entry{object_name=DN}]}} =
+ eldap:search(H,
+ #eldap_search{base = BasePath,
+ filter = eldap:substrings("sn", [{any, "a"}]),
+ scope = eldap:singleLevel()}),
+
+ %% Modify and delete the old one:
+ ok = eldap:modify_dn(H, DN, NewRDN, true, ""),
+
+ %% Check that DN was modified and the old one was deleted:
+ {ok,NewAttrs} = attributes(H, NewDN),
+ CN_new = lists:sort(proplists:get_value("cn",NewAttrs)),
+ {ok, #eldap_search_result{entries=[#eldap_entry{object_name=NewDN}]}} =
+ eldap:search(H,
+ #eldap_search{base = BasePath,
+ filter = eldap:substrings("sn", [{any, "a"}]),
+ scope = eldap:singleLevel()}),
+ %% What we expect:
+ CN_new = lists:sort([NewCN | CN_orig -- [OrigCN]]),
+
+ %% Change back:
+ ok = eldap:modify_dn(H, NewDN, OriginalRDN, true, ""),
+
+ %% Check that DN was modified and the new one was deleted:
+ {ok,SameAsOriginalAttrs} = attributes(H, DN),
+ CN_orig = lists:sort(proplists:get_value("cn",SameAsOriginalAttrs)),
+ {ok, #eldap_search_result{entries=[#eldap_entry{object_name=DN}]}} =
+ eldap:search(H,
+ #eldap_search{base = BasePath,
+ filter = eldap:substrings("sn", [{any, "a"}]),
+ scope = eldap:singleLevel()}).
+
+%%%----------------------------------------------------------------
+modify_dn_keep_old(Config) ->
+ H = ?config(handle, Config),
+ BasePath = ?config(eldap_path, Config),
+ OriginalRDN = "cn=Foo Bar",
+ DN = OriginalRDN ++ "," ++ BasePath,
+ NewCN = "Niclas Andre",
+ NewRDN = "cn="++NewCN,
+ NewDN = NewRDN ++ "," ++BasePath,
+
+ %% Check that the object to modify_dn of exists but the new one does not:
+ {ok,OriginalAttrs} = attributes(H, DN),
+ {ok, #eldap_search_result{entries=[#eldap_entry{object_name=DN}]}} =
+ eldap:search(H,
+ #eldap_search{base = BasePath,
+ filter = eldap:substrings("sn", [{any, "a"}]),
+ scope = eldap:singleLevel()}),
+
+ %% Modify but keep the old "cn" attr:
+ ok = eldap:modify_dn(H, DN, NewRDN, false, ""),
+
+ %% Check that DN was modified and the old CN entry is not deleted:
+ {ok,NewAttrs} = attributes(H, NewDN),
+ CN_orig = proplists:get_value("cn",OriginalAttrs),
+ CN_new = proplists:get_value("cn",NewAttrs),
+ Expected = lists:sort([NewCN|CN_orig]),
+ Expected = lists:sort(CN_new),
+
+ %% Restore db:
+ ok = eldap:delete(H, NewDN),
+ restore_original_object(H, DN, OriginalAttrs).
+
+%%%----------------------------------------------------------------
+%%% Test that start_tls on an already upgraded connection makes no noise
+start_tls_twice_should_fail(Config) ->
+ {ok,H} = open_bind(Config),
+ {error,tls_already_started} = eldap:start_tls(H, []),
eldap:close(H).
-start_tls_twice(doc) -> "Test that start_tls on an already upgraded connection fails";
-start_tls_twice(suite) -> [];
-start_tls_twice(Config) ->
- {Host,Port} = proplists:get_value(ldap_server, Config),
- {ok, H} = eldap:open([Host], [{port,Port}]),
- ok = eldap:start_tls(H, []),
+%%%----------------------------------------------------------------
+%%% Test that start_tls on an ldaps connection fails
+start_tls_on_ssl_should_fail(Config) ->
+ {ok,H} = open_bind(Config),
{error,tls_already_started} = eldap:start_tls(H, []),
- do_api_checks(H, Config),
eldap:close(H).
+%%%----------------------------------------------------------------
+encode(_Config) ->
+ {ok,Bin} = 'ELDAPv3':encode('AddRequest', #'AddRequest'{entry="hejHopp" ,attributes=[]} ),
+ Expected = <<104,11,4,7,104,101,106,72,111,112,112,48,0>>,
+ case Bin of
+ Expected -> ok;
+ _ -> ct:log("Encoded erroneously to:~n~p~nExpected:~n~p",[Bin,Expected]),
+ {fail, "Bad encode"}
+ end.
+
+%%%----------------------------------------------------------------
+decode(_Config) ->
+ {ok,Res} = 'ELDAPv3':decode('AddRequest', <<104,11,4,7,104,101,106,72,111,112,112,48,0>>),
+ ct:log("Res = ~p", [Res]),
+ Expected = #'AddRequest'{entry = "hejHopp",attributes = []},
+ case Res of
+ Expected -> ok;
+ #'AddRequest'{entry= <<"hejHopp">>, attributes=[]} ->
+ {fail, "decoded to (correct) binary!!"};
+ _ ->
+ {fail, "Bad decode"}
+ end.
-start_tls_on_ssl(doc) -> "Test that start_tls on an ldaps connection fails";
-start_tls_on_ssl(suite) -> [];
-start_tls_on_ssl(Config) ->
- {Host,Port} = proplists:get_value(ldaps_server, Config),
- {ok, H} = eldap:open([Host], [{port,Port}, {ssl,true}]),
- {error,tls_already_started} = eldap:start_tls(H, []),
- do_api_checks(H, Config),
- eldap:close(H).
-%%%--------------------------------------------------------------------------------
-chk_config(Key, Default, Config) ->
- case catch ct:get_config(ldap_server, undefined) of
- undefined -> [{Key,Default} | Config ];
- {'EXIT',_} -> [{Key,Default} | Config ];
- Value -> [{Key,Value} | Config]
+%%%****************************************************************
+%%% Private
+
+attributes(H, DN) ->
+ case eldap:search(H,
+ #eldap_search{base = DN,
+ filter= eldap:present("objectclass"),
+ scope = eldap:wholeSubtree()}) of
+ {ok, #eldap_search_result{entries=[#eldap_entry{object_name=DN,
+ attributes=OriginalAttrs}]}} ->
+ {ok, OriginalAttrs};
+ Other ->
+ Other
end.
+restore_original_object(H, DN, Attrs) ->
+ eldap:delete(H, DN),
+ ok = eldap:add(H, DN, Attrs).
+
+
+find_first_server(UseSSL, [{config,Key}|Ss]) ->
+ case ct:get_config(Key) of
+ {Host,Port} ->
+ ct:log("find_first_server config ~p -> ~p",[Key,{Host,Port}]),
+ find_first_server(UseSSL, [{Host,Port}|Ss]);
+ undefined ->
+ ct:log("find_first_server config ~p is undefined",[Key]),
+ find_first_server(UseSSL, Ss)
+ end;
+find_first_server(UseSSL, [{Host,Port}|Ss]) ->
+ case eldap:open([Host],[{port,Port},{ssl,UseSSL}]) of
+ {ok,H} when UseSSL==false, Ss=/=[] ->
+ case eldap:start_tls(H,[]) of
+ ok ->
+ ct:log("find_first_server ~p UseSSL=~p -> ok",[{Host,Port},UseSSL]),
+ eldap:close(H),
+ {Host,Port};
+ Res ->
+ ct:log("find_first_server ~p UseSSL=~p failed with~n~p~nSave as spare host.",[{Host,Port},UseSSL,Res]),
+ eldap:close(H),
+ find_first_server(UseSSL, Ss++[{spare_host,Host,Port}])
+ end;
+ {ok,H} ->
+ ct:log("find_first_server ~p UseSSL=~p -> ok",[{Host,Port},UseSSL]),
+ eldap:close(H),
+ {Host,Port};
+ Res ->
+ ct:log("find_first_server ~p UseSSL=~p failed with~n~p",[{Host,Port},UseSSL,Res]),
+ find_first_server(UseSSL, Ss)
+ end;
+find_first_server(false, [{spare_host,Host,Port}|_]) ->
+ ct:log("find_first_server can't find start_tls host, use the spare non-start_tls host for plain ldap: ~p",[{Host,Port}]),
+ {Host,Port};
+find_first_server(_, []) ->
+ ct:log("find_first_server, nothing left to try",[]),
+ undefined.
+
+initialize_db(Config) ->
+ case {open_bind(Config), inet:gethostname()} of
+ {{ok,H}, {ok,MyHost}} ->
+ Path = "dc="++MyHost++",dc=ericsson,dc=se",
+ delete_old_contents(H, Path),
+ add_new_contents(H, Path, MyHost),
+ eldap:close(H),
+ [{eldap_path,Path}|Config];
+ Other ->
+ ct:fail("initialize_db failed: ~p",[Other])
+ end.
+clear_db(Config) ->
+ {ok,H} = open_bind(Config),
+ Path = ?config(eldap_path, Config),
+ delete_old_contents(H, Path),
+ eldap:close(H),
+ Config.
-do_api_checks(H, Config) ->
- BasePath = proplists:get_value(eldap_path, Config),
+delete_old_contents(H, Path) ->
+ case eldap:search(H, [{base, Path},
+ {filter, eldap:present("objectclass")},
+ {scope, eldap:wholeSubtree()}])
+ of
+ {ok, #eldap_search_result{entries=Entries}} ->
+ [ok = eldap:delete(H,DN) || #eldap_entry{object_name=DN} <- Entries];
+ _Res ->
+ ignore
+ end.
- All = fun(Where) ->
- eldap:search(H, #eldap_search{base=Where,
- filter=eldap:present("objectclass"),
- scope= eldap:wholeSubtree()})
- end,
- {ok, #eldap_search_result{entries=[_XYZ]}} = All(BasePath),
-%% ct:log("XYZ=~p",[_XYZ]),
- {error, noSuchObject} = All("cn=Bar,"++BasePath),
+add_new_contents(H, Path, MyHost) ->
+ ok(eldap:add(H,"dc=ericsson,dc=se",
+ [{"objectclass", ["dcObject", "organization"]},
+ {"dc", ["ericsson"]},
+ {"o", ["Testing"]}])),
+ ok(eldap:add(H,Path,
+ [{"objectclass", ["dcObject", "organization"]},
+ {"dc", [MyHost]},
+ {"o", ["Test machine"]}])).
- {error, _} = eldap:add(H, "cn=Jonas Jonsson," ++ BasePath,
- [{"objectclass", ["person"]},
- {"cn", ["Jonas Jonsson"]}, {"sn", ["Jonsson"]}]),
- eldap:simple_bind(H, "cn=Manager,dc=ericsson,dc=se", "hejsan"),
- chk_add(H, BasePath),
- {ok,FB} = chk_search(H, BasePath),
- chk_modify(H, FB),
- chk_delete(H, BasePath),
- chk_modify_dn(H, FB).
+ok({error,entryAlreadyExists}) -> ok;
+ok(X) -> ok=X.
-chk_add(H, BasePath) ->
- ok = eldap:add(H, "cn=Jonas Jonsson," ++ BasePath,
- [{"objectclass", ["person"]},
- {"cn", ["Jonas Jonsson"]}, {"sn", ["Jonsson"]}]),
- {error, entryAlreadyExists} = eldap:add(H, "cn=Jonas Jonsson," ++ BasePath,
- [{"objectclass", ["person"]},
- {"cn", ["Jonas Jonsson"]}, {"sn", ["Jonsson"]}]),
- ok = eldap:add(H, "cn=Foo Bar," ++ BasePath,
- [{"objectclass", ["person"]},
- {"cn", ["Foo Bar"]}, {"sn", ["Bar"]}, {"telephoneNumber", ["555-1232", "555-5432"]}]),
- ok = eldap:add(H, "ou=Team," ++ BasePath,
- [{"objectclass", ["organizationalUnit"]},
- {"ou", ["Team"]}]).
-chk_search(H, BasePath) ->
- Search = fun(Filter) ->
- eldap:search(H, #eldap_search{base=BasePath,
- filter=Filter,
- scope=eldap:singleLevel()})
- end,
- JJSR = {ok, #eldap_search_result{entries=[#eldap_entry{}]}} = Search(eldap:equalityMatch("sn", "Jonsson")),
- JJSR = Search(eldap:substrings("sn", [{any, "ss"}])),
- FBSR = {ok, #eldap_search_result{entries=[#eldap_entry{object_name=FB}]}} =
- Search(eldap:substrings("sn", [{any, "a"}])),
- FBSR = Search(eldap:substrings("sn", [{initial, "B"}])),
- FBSR = Search(eldap:substrings("sn", [{final, "r"}])),
- F_AND = eldap:'and'([eldap:present("objectclass"), eldap:present("ou")]),
- {ok, #eldap_search_result{entries=[#eldap_entry{}]}} = Search(F_AND),
- F_NOT = eldap:'and'([eldap:present("objectclass"), eldap:'not'(eldap:present("ou"))]),
- {ok, #eldap_search_result{entries=[#eldap_entry{}, #eldap_entry{}]}} = Search(F_NOT),
- {ok, #eldap_search_result{entries=[#eldap_entry{}]}} = Search(eldap:extensibleMatch("Bar",[{type,"sn"},{matchingRule,"caseExactMatch"}])),
- {ok, #eldap_search_result{entries=[#eldap_entry{}]}} = Search(eldap:extensibleMatch("Bar",[{type,"sn"},{matchingRule,"2.5.13.5"}])),
- {ok, #eldap_search_result{entries=[#eldap_entry{}]}} = Search(eldap:extensibleMatch("Bar",[{type,"sn"},{matchingRule,"caseIgnoreMatch"}])),
- {ok, #eldap_search_result{entries=[#eldap_entry{}]}} = Search(eldap:extensibleMatch("bar",[{type,"sn"},{matchingRule,"caseIgnoreMatch"}])),
- {ok, #eldap_search_result{entries=[]}} = Search(eldap:extensibleMatch("bar",[{type,"sn"},{matchingRule,"gluffgluff"}])),
- {ok, #eldap_search_result{entries=[]}} = Search(eldap:extensibleMatch("bar",[{type,"sn"},{matchingRule,"caseExactMatch"}])),
- {ok,FB}. %% FIXME
-
-chk_modify(H, FB) ->
- Mod = [eldap:mod_replace("telephoneNumber", ["555-12345"]),
- eldap:mod_add("description", ["Nice guy"])],
- %% io:format("MOD ~p ~p ~n",[FB, Mod]),
- ok = eldap:modify(H, FB, Mod),
- %% DELETE ATTR
- ok = eldap:modify(H, FB, [eldap:mod_delete("telephoneNumber", [])]).
-
-
-chk_delete(H, BasePath) ->
- {error, entryAlreadyExists} = eldap:add(H, "cn=Jonas Jonsson," ++ BasePath,
- [{"objectclass", ["person"]},
- {"cn", ["Jonas Jonsson"]}, {"sn", ["Jonsson"]}]),
- ok = eldap:delete(H, "cn=Jonas Jonsson," ++ BasePath),
- {error, noSuchObject} = eldap:delete(H, "cn=Jonas Jonsson," ++ BasePath).
-
-chk_modify_dn(H, FB) ->
- ok = eldap:modify_dn(H, FB, "cn=Niclas Andre", true, "").
- %%io:format("Res ~p~n ~p~n",[R, All(BasePath)]).
-
-
-%%%----------------
-add(H, Attr, Value, Path0, Attrs, Class) ->
- Path = case Path0 of
- [] -> Attr ++ "=" ++ Value;
- _ -> Attr ++ "=" ++ Value ++ "," ++ Path0
- end,
- case eldap:add(H, Path, [{"objectclass", Class}, {Attr, [Value]}] ++ Attrs)
- of
- ok -> {ok, Path};
- {error, E = entryAlreadyExists} -> {E, Path};
- R = {error, Reason} ->
- io:format("~p:~p: ~s,~s =>~n ~p~n",
- [?MODULE,?LINE, Attr, Value, R]),
- exit({ldap, add, Reason})
+cond_start_tls(H, Config) ->
+ case ?config(start_tls,Config) of
+ true -> start_tls(H,Config);
+ _ -> Config
end.
+start_tls(H, Config) ->
+ KeyFile = filename:join([?config(data_dir,Config),
+ "certs/client/key.pem"
+ ]),
+ case eldap:start_tls(H, [{keyfile, KeyFile}]) of
+ ok ->
+ [{start_tls_success,true} | Config];
+ Error ->
+ ct:log("Start_tls on ~p failed: ~p",[?config(url,Config) ,Error]),
+ ct:fail("start_tls failed")
+ end.
-%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
-%% Develop
-%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
-
-test() ->
- run().
-
-run() ->
- Cases = all(),
- run(Cases).
-
-run(Case) when is_atom(Case) ->
- run([Case]);
-run(Cases) when is_list(Cases) ->
- Run = fun(Test, Config0) ->
- Config = init_per_testcase(Test, Config0),
- try
- io:format("~nTest ~p ... ",[Test]),
- ?MODULE:Test(Config),
- end_per_testcase(Test, Config),
- io:format("ok~n",[])
- catch _:Reason ->
- io:format("~n FAIL (~p): ~p~n ~p~n",
- [Test, Reason, erlang:get_stacktrace()])
- end
- end,
- process_flag(trap_exit, true),
- Pid = spawn_link(fun() ->
- case init_per_suite([]) of
- {skip, Reason} -> io:format("Skip ~s~n",[Reason]);
- Config ->
- try
- [Run(Test, Config) || Test <- Cases]
- catch _:Err ->
- io:format("Error ~p in ~p~n",[Err, erlang:get_stacktrace()])
- end,
- end_per_suite(Config)
- end
- end),
- receive
- {'EXIT', Pid, normal} -> ok;
- Msg -> io:format("Received ~p (~p)~n",[Msg, Pid])
- after 100 -> ok end,
- process_flag(trap_exit, false),
- ok.
+%%%----------------------------------------------------------------
+open_bind(Config) ->
+ {ok,H} = open(Config),
+ ok = eldap:simple_bind(H, "cn=Manager,dc=ericsson,dc=se", "hejsan"),
+ {ok,H}.
+
+open(Config) ->
+ {Host,Port} = ?config(server,Config),
+ SSLflag = ?config(ssl_flag,Config),
+ {ok,H} = eldap:open([Host], [{port,Port},{ssl,SSLflag}]),
+ cond_start_tls(H, Config),
+ {ok,H}.
+
+%%%----------------------------------------------------------------
+supported_extension(OID, Config) ->
+ {ok,H} = open_bind(Config),
+ case eldap:search(H, [{scope, eldap:baseObject()},
+ {filter, eldap:present("objectclass")},
+ {deref, eldap:neverDerefAliases()},
+ {attributes, ["+"]}]) of
+ {ok,R=#eldap_search_result{}} ->
+ eldap:close(H),
+ lists:member(OID,
+ [SE || EE <- R#eldap_search_result.entries,
+ {"supportedExtension",SEs} <- EE#eldap_entry.attributes,
+ SE<-SEs]);
+ _ ->
+ eldap:close(H),
+ false
+ end.
+
+%%%----------------------------------------------------------------
+client_timeout(Fun, Config) ->
+ Host = proplists:get_value(listen_host, Config),
+ Port = proplists:get_value(listen_port, Config),
+ Opts = proplists:get_value(tcp_connect_opts, Config),
+ T = 1000,
+ case eldap:open([Host], [{timeout,T},{port,Port}|Opts]) of
+ {ok,H} ->
+ T0 = now(),
+ {error,{gen_tcp_error,timeout}} = Fun(H),
+ T_op = diff(T0,now()),
+ ct:log("Time = ~p, Timeout spec = ~p",[T_op,T]),
+ if
+ T_op < T ->
+ {fail, "Timeout too early"};
+ true ->
+ ok
+ end;
+
+ Other -> ct:fail("eldap:open failed: ~p",[Other])
+ end.
+
+diff({M1,S1,U1},{M2,S2,U2}) ->
+ ( ((M2-M1)*1000 + (S2-S1))*1000 + (U2-U1) ).
+
+%%%----------------------------------------------------------------
+init_ssl_certs_et_al(Config) ->
+ try ssl:start()
+ of
+ R when R==ok ; R=={error,{already_started,ssl}} ->
+ try make_certs:all("/dev/null",
+ filename:join(?config(data_dir,Config), "certs"))
+ of
+ {ok,_} -> true;
+ Other ->
+ ct:comment("make_certs failed"),
+ ct:log("make_certs failed ~p", [Other]),
+ false
+ catch
+ C:E ->
+ ct:comment("make_certs crashed"),
+ ct:log("make_certs failed ~p:~p", [C,E]),
+ false
+ end;
+ _ ->
+ false
+ catch
+ Error:Reason ->
+ ct:comment("ssl failed to start"),
+ ct:log("init_per_suite failed to start ssl Error=~p Reason=~p", [Error, Reason]),
+ false
+ end.
diff --git a/lib/eldap/test/eldap_basic_SUITE_data/RAND b/lib/eldap/test/eldap_basic_SUITE_data/RAND
new file mode 100644
index 0000000000..70997bd01f
--- /dev/null
+++ b/lib/eldap/test/eldap_basic_SUITE_data/RAND
Binary files differ
diff --git a/lib/eldap/test/eldap_connections_SUITE.erl b/lib/eldap/test/eldap_connections_SUITE.erl
deleted file mode 100644
index c5460fef09..0000000000
--- a/lib/eldap/test/eldap_connections_SUITE.erl
+++ /dev/null
@@ -1,147 +0,0 @@
-%%
-%% %CopyrightBegin%
-%%
-%% Copyright Ericsson AB 2012-2014. 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(eldap_connections_SUITE).
-
--compile(export_all).
-
--include_lib("common_test/include/ct.hrl").
-%-include_lib("eldap/include/eldap.hrl").
-
-
-all() ->
- [
- {group, v4},
- {group, v6}
- ].
-
-
-init_per_group(v4, Config) ->
- [{listen_opts, []},
- {listen_host, "localhost"},
- {connect_opts, []}
- | Config];
-init_per_group(v6, Config) ->
- {ok, Hostname} = inet:gethostname(),
- case lists:member(list_to_atom(Hostname), ct:get_config(ipv6_hosts,[])) of
- true ->
- [{listen_opts, [inet6]},
- {listen_host, "::"},
- {connect_opts, [{tcpopts,[inet6]}]}
- | Config];
- false ->
- {skip, io_lib:format("~p is not an ipv6_host",[Hostname])}
- end.
-
-
-end_per_group(_GroupName, Config) ->
- Config.
-
-
-groups() ->
- [{v4, [], [tcp_connection, tcp_connection_option]},
- {v6, [], [tcp_connection, tcp_connection_option]}
- ].
-
-
-init_per_suite(Config) -> Config.
-
-
-end_per_suite(_Config) -> ok.
-
-
-init_per_testcase(_TestCase, Config) ->
- case gen_tcp:listen(0, proplists:get_value(listen_opts,Config)) of
- {ok,LSock} ->
- {ok,{_,Port}} = inet:sockname(LSock),
- [{listen_socket,LSock},
- {listen_port,Port}
- | Config];
- Other ->
- {fail, Other}
- end.
-
-
-end_per_testcase(_TestCase, Config) ->
- catch gen_tcp:close( proplists:get_value(listen_socket, Config) ).
-
-%%%================================================================
-%%%
-%%% Test cases
-%%%
-%%%----------------------------------------------------------------
-tcp_connection(Config) ->
- Host = proplists:get_value(listen_host, Config),
- Port = proplists:get_value(listen_port, Config),
- Opts = proplists:get_value(connect_opts, Config),
- case eldap:open([Host], [{port,Port}|Opts]) of
- {ok,_H} ->
- Sl = proplists:get_value(listen_socket, Config),
- case gen_tcp:accept(Sl,1000) of
- {ok,_S} -> ok;
- {error,timeout} -> ct:fail("server side accept timeout",[])
- end;
- Other -> ct:fail("eldap:open failed: ~p",[Other])
- end.
-
-
-%%%----------------------------------------------------------------
-tcp_connection_option(Config) ->
- Host = proplists:get_value(listen_host, Config),
- Port = proplists:get_value(listen_port, Config),
- Opts = proplists:get_value(connect_opts, Config),
- Sl = proplists:get_value(listen_socket, Config),
-
- %% Make an option value to test. The option must be implemented on all
- %% platforms that we test on. Must check what the default value is
- %% so we don't happen to choose that particular value.
- {ok,[{linger,DefaultLinger}]} = inet:getopts(Sl, [linger]),
- TestLinger = case DefaultLinger of
- {false,_} -> {true,5};
- {true,_} -> {false,0}
- end,
-
- case catch eldap:open([Host],
- [{port,Port},{tcpopts,[{linger,TestLinger}]}|Opts]) of
- {ok,H} ->
- case gen_tcp:accept(Sl,1000) of
- {ok,_} ->
- case eldap:getopts(H, [{tcpopts,[linger]}]) of
- {ok,[{tcpopts,[{linger,ActualLinger}]}]} ->
- case ActualLinger of
- TestLinger ->
- ok;
- DefaultLinger ->
- ct:fail("eldap:getopts: 'linger' didn't change,"
- " got ~p (=default) expected ~p",
- [ActualLinger,TestLinger]);
- _ ->
- ct:fail("eldap:getopts: bad 'linger', got ~p expected ~p",
- [ActualLinger,TestLinger])
- end;
- Other ->
- ct:fail("eldap:getopts: bad result ~p",[Other])
- end;
- {error,timeout} ->
- ct:fail("server side accept timeout",[])
- end;
-
- Other ->
- ct:fail("eldap:open failed: ~p",[Other])
- end.
diff --git a/lib/eldap/test/eldap_misc_SUITE.erl b/lib/eldap/test/eldap_misc_SUITE.erl
deleted file mode 100644
index ca810ee33c..0000000000
--- a/lib/eldap/test/eldap_misc_SUITE.erl
+++ /dev/null
@@ -1,51 +0,0 @@
-%%
-%% %CopyrightBegin%
-%%
-%% Copyright Ericsson AB 2012-2014. 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(eldap_misc_SUITE).
-
--compile(export_all). %% Use this only in test suites...
-
--include_lib("common_test/include/ct.hrl").
--include_lib("eldap/include/eldap.hrl").
--include_lib("eldap/ebin/ELDAPv3.hrl").
-
-all() ->
- [
- encode,
- decode
- ].
-
-
-encode(_Config) ->
- {ok,Bin} = 'ELDAPv3':encode('AddRequest', #'AddRequest'{entry="hejHopp" ,attributes=[]} ),
- Expected = <<104,11,4,7,104,101,106,72,111,112,112,48,0>>,
- Expected = Bin.
-
-decode(_Config) ->
- {ok,Res} = 'ELDAPv3':decode('AddRequest', <<104,11,4,7,104,101,106,72,111,112,112,48,0>>),
- ct:log("Res = ~p", [Res]),
- Expected = #'AddRequest'{entry = "hejHopp",attributes = []},
- case Res of
- Expected -> ok;
- #'AddRequest'{entry= <<"hejHopp">>, attributes=[]} ->
- {fail, "decoded to (correct) binary!!"};
- _ ->
- {fail, "Bad decode"}
- end.
-
diff --git a/lib/eldap/test/make_certs.erl b/lib/eldap/test/make_certs.erl
index f963af180d..15a7e118ff 100644
--- a/lib/eldap/test/make_certs.erl
+++ b/lib/eldap/test/make_certs.erl
@@ -1,41 +1,89 @@
%%
%% %CopyrightBegin%
-%%
-%% Copyright Ericsson AB 2007-2013. All Rights Reserved.
-%%
+%%
+%% Copyright Ericsson AB 2007-2012. 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(make_certs).
+-compile([export_all]).
--export([all/2]).
+%-export([all/1, all/2, rootCA/2, intermediateCA/3, endusers/3, enduser/3, revoke/3, gencrl/2, verify/3]).
--record(dn, {commonName,
+-record(config, {commonName,
organizationalUnitName = "Erlang OTP",
organizationName = "Ericsson AB",
localityName = "Stockholm",
countryName = "SE",
- emailAddress = "[email protected]"}).
+ emailAddress = "[email protected]",
+ default_bits = 2048,
+ v2_crls = true,
+ ecc_certs = false,
+ issuing_distribution_point = false,
+ crl_port = 8000,
+ openssl_cmd = "openssl"}).
+
+
+default_config() ->
+ #config{}.
+
+make_config(Args) ->
+ make_config(Args, #config{}).
+
+make_config([], C) ->
+ C;
+make_config([{organizationalUnitName, Name}|T], C) when is_list(Name) ->
+ make_config(T, C#config{organizationalUnitName = Name});
+make_config([{organizationName, Name}|T], C) when is_list(Name) ->
+ make_config(T, C#config{organizationName = Name});
+make_config([{localityName, Name}|T], C) when is_list(Name) ->
+ make_config(T, C#config{localityName = Name});
+make_config([{countryName, Name}|T], C) when is_list(Name) ->
+ make_config(T, C#config{countryName = Name});
+make_config([{emailAddress, Name}|T], C) when is_list(Name) ->
+ make_config(T, C#config{emailAddress = Name});
+make_config([{default_bits, Bits}|T], C) when is_integer(Bits) ->
+ make_config(T, C#config{default_bits = Bits});
+make_config([{v2_crls, Bool}|T], C) when is_boolean(Bool) ->
+ make_config(T, C#config{v2_crls = Bool});
+make_config([{crl_port, Port}|T], C) when is_integer(Port) ->
+ make_config(T, C#config{crl_port = Port});
+make_config([{ecc_certs, Bool}|T], C) when is_boolean(Bool) ->
+ make_config(T, C#config{ecc_certs = Bool});
+make_config([{issuing_distribution_point, Bool}|T], C) when is_boolean(Bool) ->
+ make_config(T, C#config{issuing_distribution_point = Bool});
+make_config([{openssl_cmd, Cmd}|T], C) when is_list(Cmd) ->
+ make_config(T, C#config{openssl_cmd = Cmd}).
+
+
+all([DataDir, PrivDir]) ->
+ all(DataDir, PrivDir).
all(DataDir, PrivDir) ->
- OpenSSLCmd = "openssl",
+ all(DataDir, PrivDir, #config{}).
+
+all(DataDir, PrivDir, C) when is_list(C) ->
+ all(DataDir, PrivDir, make_config(C));
+all(DataDir, PrivDir, C = #config{}) ->
+ ok = filelib:ensure_dir(filename:join(PrivDir, "erlangCA")),
create_rnd(DataDir, PrivDir), % For all requests
- rootCA(PrivDir, OpenSSLCmd, "erlangCA"),
- intermediateCA(PrivDir, OpenSSLCmd, "otpCA", "erlangCA"),
- endusers(PrivDir, OpenSSLCmd, "otpCA", ["client", "server"]),
- collect_certs(PrivDir, ["erlangCA", "otpCA"], ["client", "server"]),
- %% Create keycert files
+ rootCA(PrivDir, "erlangCA", C),
+ intermediateCA(PrivDir, "otpCA", "erlangCA", C),
+ endusers(PrivDir, "otpCA", ["client", "server", "revoked"], C),
+ endusers(PrivDir, "erlangCA", ["localhost"], C),
+ %% Create keycert files
SDir = filename:join([PrivDir, "server"]),
SC = filename:join([SDir, "cert.pem"]),
SK = filename:join([SDir, "key.pem"]),
@@ -46,7 +94,14 @@ all(DataDir, PrivDir) ->
CK = filename:join([CDir, "key.pem"]),
CKC = filename:join([CDir, "keycert.pem"]),
append_files([CK, CC], CKC),
- remove_rnd(PrivDir).
+ RDir = filename:join([PrivDir, "revoked"]),
+ RC = filename:join([RDir, "cert.pem"]),
+ RK = filename:join([RDir, "key.pem"]),
+ RKC = filename:join([RDir, "keycert.pem"]),
+ revoke(PrivDir, "otpCA", "revoked", C),
+ append_files([RK, RC], RKC),
+ remove_rnd(PrivDir),
+ {ok, C}.
append_files(FileNames, ResultFileName) ->
{ok, ResultFile} = file:open(ResultFileName, [write]),
@@ -59,117 +114,182 @@ do_append_files([F|Fs], RF) ->
ok = file:write(RF, Data),
do_append_files(Fs, RF).
-rootCA(Root, OpenSSLCmd, Name) ->
- create_ca_dir(Root, Name, ca_cnf(Name)),
- DN = #dn{commonName = Name},
- create_self_signed_cert(Root, OpenSSLCmd, Name, req_cnf(DN)),
- ok.
+rootCA(Root, Name, C) ->
+ create_ca_dir(Root, Name, ca_cnf(C#config{commonName = Name})),
+ create_self_signed_cert(Root, Name, req_cnf(C#config{commonName = Name}), C),
+ file:copy(filename:join([Root, Name, "cert.pem"]), filename:join([Root, Name, "cacerts.pem"])),
+ gencrl(Root, Name, C).
-intermediateCA(Root, OpenSSLCmd, CA, ParentCA) ->
- CA = "otpCA",
- create_ca_dir(Root, CA, ca_cnf(CA)),
+intermediateCA(Root, CA, ParentCA, C) ->
+ create_ca_dir(Root, CA, ca_cnf(C#config{commonName = CA})),
CARoot = filename:join([Root, CA]),
- DN = #dn{commonName = CA},
CnfFile = filename:join([CARoot, "req.cnf"]),
- file:write_file(CnfFile, req_cnf(DN)),
- KeyFile = filename:join([CARoot, "private", "key.pem"]),
- ReqFile = filename:join([CARoot, "req.pem"]),
- create_req(Root, OpenSSLCmd, CnfFile, KeyFile, ReqFile),
+ file:write_file(CnfFile, req_cnf(C#config{commonName = CA})),
+ KeyFile = filename:join([CARoot, "private", "key.pem"]),
+ ReqFile = filename:join([CARoot, "req.pem"]),
+ create_req(Root, CnfFile, KeyFile, ReqFile, C),
CertFile = filename:join([CARoot, "cert.pem"]),
- sign_req(Root, OpenSSLCmd, ParentCA, "ca_cert", ReqFile, CertFile).
-
-endusers(Root, OpenSSLCmd, CA, Users) ->
- lists:foreach(fun(User) -> enduser(Root, OpenSSLCmd, CA, User) end, Users).
-
-enduser(Root, OpenSSLCmd, CA, User) ->
+ sign_req(Root, ParentCA, "ca_cert", ReqFile, CertFile, C),
+ CACertsFile = filename:join(CARoot, "cacerts.pem"),
+ file:copy(filename:join([Root, ParentCA, "cacerts.pem"]), CACertsFile),
+ %% append this CA's cert to the cacerts file
+ {ok, Bin} = file:read_file(CertFile),
+ {ok, FD} = file:open(CACertsFile, [append]),
+ file:write(FD, ["\n", Bin]),
+ file:close(FD),
+ gencrl(Root, CA, C).
+
+endusers(Root, CA, Users, C) ->
+ [enduser(Root, CA, User, C) || User <- Users].
+
+enduser(Root, CA, User, C) ->
UsrRoot = filename:join([Root, User]),
file:make_dir(UsrRoot),
CnfFile = filename:join([UsrRoot, "req.cnf"]),
- DN = #dn{commonName = User},
- file:write_file(CnfFile, req_cnf(DN)),
- KeyFile = filename:join([UsrRoot, "key.pem"]),
- ReqFile = filename:join([UsrRoot, "req.pem"]),
- create_req(Root, OpenSSLCmd, CnfFile, KeyFile, ReqFile),
+ file:write_file(CnfFile, req_cnf(C#config{commonName = User})),
+ KeyFile = filename:join([UsrRoot, "key.pem"]),
+ ReqFile = filename:join([UsrRoot, "req.pem"]),
+ create_req(Root, CnfFile, KeyFile, ReqFile, C),
+ %create_req(Root, CnfFile, KeyFile, ReqFile),
CertFileAllUsage = filename:join([UsrRoot, "cert.pem"]),
- sign_req(Root, OpenSSLCmd, CA, "user_cert", ReqFile, CertFileAllUsage),
+ sign_req(Root, CA, "user_cert", ReqFile, CertFileAllUsage, C),
CertFileDigitalSigOnly = filename:join([UsrRoot, "digital_signature_only_cert.pem"]),
- sign_req(Root, OpenSSLCmd, CA, "user_cert_digital_signature_only", ReqFile, CertFileDigitalSigOnly).
-
-collect_certs(Root, CAs, Users) ->
- Bins = lists:foldr(
- fun(CA, Acc) ->
- File = filename:join([Root, CA, "cert.pem"]),
- {ok, Bin} = file:read_file(File),
- [Bin, "\n" | Acc]
- end, [], CAs),
- lists:foreach(
- fun(User) ->
- File = filename:join([Root, User, "cacerts.pem"]),
- file:write_file(File, Bins)
- end, Users).
+ sign_req(Root, CA, "user_cert_digital_signature_only", ReqFile, CertFileDigitalSigOnly, C),
+ CACertsFile = filename:join(UsrRoot, "cacerts.pem"),
+ file:copy(filename:join([Root, CA, "cacerts.pem"]), CACertsFile),
+ ok.
-create_self_signed_cert(Root, OpenSSLCmd, CAName, Cnf) ->
+revoke(Root, CA, User, C) ->
+ UsrCert = filename:join([Root, User, "cert.pem"]),
+ CACnfFile = filename:join([Root, CA, "ca.cnf"]),
+ Cmd = [C#config.openssl_cmd, " ca"
+ " -revoke ", UsrCert,
+ [" -crl_reason keyCompromise" || C#config.v2_crls ],
+ " -config ", CACnfFile],
+ Env = [{"ROOTDIR", filename:absname(Root)}],
+ cmd(Cmd, Env),
+ gencrl(Root, CA, C).
+
+gencrl(Root, CA, C) ->
+ CACnfFile = filename:join([Root, CA, "ca.cnf"]),
+ CACRLFile = filename:join([Root, CA, "crl.pem"]),
+ Cmd = [C#config.openssl_cmd, " ca"
+ " -gencrl ",
+ " -crlhours 24",
+ " -out ", CACRLFile,
+ " -config ", CACnfFile],
+ Env = [{"ROOTDIR", filename:absname(Root)}],
+ cmd(Cmd, Env).
+
+verify(Root, CA, User, C) ->
+ CAFile = filename:join([Root, User, "cacerts.pem"]),
+ CACRLFile = filename:join([Root, CA, "crl.pem"]),
+ CertFile = filename:join([Root, User, "cert.pem"]),
+ Cmd = [C#config.openssl_cmd, " verify"
+ " -CAfile ", CAFile,
+ " -CRLfile ", CACRLFile, %% this is undocumented, but seems to work
+ " -crl_check ",
+ CertFile],
+ Env = [{"ROOTDIR", filename:absname(Root)}],
+ try cmd(Cmd, Env) catch
+ exit:{eval_cmd, _, _} ->
+ invalid
+ end.
+
+create_self_signed_cert(Root, CAName, Cnf, C = #config{ecc_certs = true}) ->
CARoot = filename:join([Root, CAName]),
CnfFile = filename:join([CARoot, "req.cnf"]),
file:write_file(CnfFile, Cnf),
- KeyFile = filename:join([CARoot, "private", "key.pem"]),
- CertFile = filename:join([CARoot, "cert.pem"]),
- Cmd = [OpenSSLCmd, " req"
+ KeyFile = filename:join([CARoot, "private", "key.pem"]),
+ CertFile = filename:join([CARoot, "cert.pem"]),
+ Cmd = [C#config.openssl_cmd, " ecparam"
+ " -out ", KeyFile,
+ " -name secp521r1 ",
+ %" -name sect283k1 ",
+ " -genkey "],
+ Env = [{"ROOTDIR", filename:absname(Root)}],
+ cmd(Cmd, Env),
+
+ Cmd2 = [C#config.openssl_cmd, " req"
+ " -new"
+ " -x509"
+ " -config ", CnfFile,
+ " -key ", KeyFile,
+ " -outform PEM ",
+ " -out ", CertFile],
+ cmd(Cmd2, Env);
+create_self_signed_cert(Root, CAName, Cnf, C) ->
+ CARoot = filename:join([Root, CAName]),
+ CnfFile = filename:join([CARoot, "req.cnf"]),
+ file:write_file(CnfFile, Cnf),
+ KeyFile = filename:join([CARoot, "private", "key.pem"]),
+ CertFile = filename:join([CARoot, "cert.pem"]),
+ Cmd = [C#config.openssl_cmd, " req"
" -new"
" -x509"
" -config ", CnfFile,
" -keyout ", KeyFile,
- " -out ", CertFile],
- Env = [{"ROOTDIR", Root}],
- cmd(Cmd, Env),
- fix_key_file(OpenSSLCmd, KeyFile).
-
-% openssl 1.0 generates key files in pkcs8 format by default and we don't handle this format
-fix_key_file(OpenSSLCmd, KeyFile) ->
- KeyFileTmp = KeyFile ++ ".tmp",
- Cmd = [OpenSSLCmd, " rsa",
- " -in ",
- KeyFile,
- " -out ",
- KeyFileTmp],
- cmd(Cmd, []),
- ok = file:rename(KeyFileTmp, KeyFile).
+ " -outform PEM",
+ " -out ", CertFile],
+ Env = [{"ROOTDIR", filename:absname(Root)}],
+ cmd(Cmd, Env).
+
create_ca_dir(Root, CAName, Cnf) ->
CARoot = filename:join([Root, CAName]),
+ ok = filelib:ensure_dir(CARoot),
file:make_dir(CARoot),
create_dirs(CARoot, ["certs", "crl", "newcerts", "private"]),
create_rnd(Root, filename:join([CAName, "private"])),
create_files(CARoot, [{"serial", "01\n"},
+ {"crlnumber", "01"},
{"index.txt", ""},
{"ca.cnf", Cnf}]).
-create_req(Root, OpenSSLCmd, CnfFile, KeyFile, ReqFile) ->
- Cmd = [OpenSSLCmd, " req"
+create_req(Root, CnfFile, KeyFile, ReqFile, C = #config{ecc_certs = true}) ->
+ Cmd = [C#config.openssl_cmd, " ecparam"
+ " -out ", KeyFile,
+ " -name secp521r1 ",
+ %" -name sect283k1 ",
+ " -genkey "],
+ Env = [{"ROOTDIR", filename:absname(Root)}],
+ cmd(Cmd, Env),
+ Cmd2 = [C#config.openssl_cmd, " req"
+ " -new ",
+ " -key ", KeyFile,
+ " -outform PEM ",
+ " -out ", ReqFile,
+ " -config ", CnfFile],
+ cmd(Cmd2, Env);
+ %fix_key_file(KeyFile).
+create_req(Root, CnfFile, KeyFile, ReqFile, C) ->
+ Cmd = [C#config.openssl_cmd, " req"
" -new"
" -config ", CnfFile,
- " -keyout ", KeyFile,
- " -out ", ReqFile],
- Env = [{"ROOTDIR", Root}],
- cmd(Cmd, Env),
- fix_key_file(OpenSSLCmd, KeyFile).
+ " -outform PEM ",
+ " -keyout ", KeyFile,
+ " -out ", ReqFile],
+ Env = [{"ROOTDIR", filename:absname(Root)}],
+ cmd(Cmd, Env).
+ %fix_key_file(KeyFile).
+
-sign_req(Root, OpenSSLCmd, CA, CertType, ReqFile, CertFile) ->
+sign_req(Root, CA, CertType, ReqFile, CertFile, C) ->
CACnfFile = filename:join([Root, CA, "ca.cnf"]),
- Cmd = [OpenSSLCmd, " ca"
+ Cmd = [C#config.openssl_cmd, " ca"
" -batch"
" -notext"
- " -config ", CACnfFile,
+ " -config ", CACnfFile,
" -extensions ", CertType,
- " -in ", ReqFile,
+ " -in ", ReqFile,
" -out ", CertFile],
- Env = [{"ROOTDIR", Root}],
+ Env = [{"ROOTDIR", filename:absname(Root)}],
cmd(Cmd, Env).
-
+
%%
%% Misc
%%
-
+
create_dirs(Root, Dirs) ->
lists:foreach(fun(Dir) ->
file:make_dir(filename:join([Root, Dir])) end,
@@ -192,30 +312,30 @@ remove_rnd(Dir) ->
cmd(Cmd, Env) ->
FCmd = lists:flatten(Cmd),
- Port = open_port({spawn, FCmd}, [stream, eof, exit_status, stderr_to_stdout,
+ Port = open_port({spawn, FCmd}, [stream, eof, exit_status, stderr_to_stdout,
{env, Env}]),
- eval_cmd(Port).
+ eval_cmd(Port, FCmd).
-eval_cmd(Port) ->
- receive
+eval_cmd(Port, Cmd) ->
+ receive
{Port, {data, _}} ->
- eval_cmd(Port);
+ eval_cmd(Port, Cmd);
{Port, eof} ->
ok
end,
receive
{Port, {exit_status, Status}} when Status /= 0 ->
%% io:fwrite("exit status: ~w~n", [Status]),
- exit({eval_cmd, Status})
+ exit({eval_cmd, Cmd, Status})
after 0 ->
ok
end.
%%
-%% Contents of configuration files
+%% Contents of configuration files
%%
-req_cnf(DN) ->
+req_cnf(C) ->
["# Purpose: Configuration for requests (end users and CAs)."
"\n"
"ROOTDIR = $ENV::ROOTDIR\n"
@@ -224,10 +344,10 @@ req_cnf(DN) ->
"[req]\n"
"input_password = secret\n"
"output_password = secret\n"
- "default_bits = 1024\n"
+ "default_bits = ", integer_to_list(C#config.default_bits), "\n"
"RANDFILE = $ROOTDIR/RAND\n"
"encrypt_key = no\n"
- "default_md = sha1\n"
+ "default_md = md5\n"
"#string_mask = pkix\n"
"x509_extensions = ca_ext\n"
"prompt = no\n"
@@ -235,12 +355,12 @@ req_cnf(DN) ->
"\n"
"[name]\n"
- "commonName = ", DN#dn.commonName, "\n"
- "organizationalUnitName = ", DN#dn.organizationalUnitName, "\n"
- "organizationName = ", DN#dn.organizationName, "\n"
- "localityName = ", DN#dn.localityName, "\n"
- "countryName = ", DN#dn.countryName, "\n"
- "emailAddress = ", DN#dn.emailAddress, "\n"
+ "commonName = ", C#config.commonName, "\n"
+ "organizationalUnitName = ", C#config.organizationalUnitName, "\n"
+ "organizationName = ", C#config.organizationName, "\n"
+ "localityName = ", C#config.localityName, "\n"
+ "countryName = ", C#config.countryName, "\n"
+ "emailAddress = ", C#config.emailAddress, "\n"
"\n"
"[ca_ext]\n"
@@ -249,8 +369,7 @@ req_cnf(DN) ->
"subjectKeyIdentifier = hash\n"
"subjectAltName = email:copy\n"].
-
-ca_cnf(CA) ->
+ca_cnf(C) ->
["# Purpose: Configuration for CAs.\n"
"\n"
"ROOTDIR = $ENV::ROOTDIR\n"
@@ -258,21 +377,23 @@ ca_cnf(CA) ->
"\n"
"[ca]\n"
- "dir = $ROOTDIR/", CA, "\n"
+ "dir = $ROOTDIR/", C#config.commonName, "\n"
"certs = $dir/certs\n"
"crl_dir = $dir/crl\n"
"database = $dir/index.txt\n"
"new_certs_dir = $dir/newcerts\n"
"certificate = $dir/cert.pem\n"
"serial = $dir/serial\n"
- "crl = $dir/crl.pem\n"
+ "crl = $dir/crl.pem\n",
+ ["crlnumber = $dir/crlnumber\n" || C#config.v2_crls],
"private_key = $dir/private/key.pem\n"
"RANDFILE = $dir/private/RAND\n"
"\n"
- "x509_extensions = user_cert\n"
+ "x509_extensions = user_cert\n",
+ ["crl_extensions = crl_ext\n" || C#config.v2_crls],
"unique_subject = no\n"
"default_days = 3600\n"
- "default_md = sha1\n"
+ "default_md = md5\n"
"preserve = no\n"
"policy = policy_match\n"
"\n"
@@ -286,6 +407,13 @@ ca_cnf(CA) ->
"emailAddress = supplied\n"
"\n"
+ "[crl_ext]\n"
+ "authorityKeyIdentifier=keyid:always,issuer:always\n",
+ ["issuingDistributionPoint=critical, @idpsec\n" || C#config.issuing_distribution_point],
+
+ "[idpsec]\n"
+ "fullname=URI:http://localhost:8000/",C#config.commonName,"/crl.pem\n"
+
"[user_cert]\n"
"basicConstraints = CA:false\n"
"keyUsage = nonRepudiation, digitalSignature, keyEncipherment\n"
@@ -293,6 +421,12 @@ ca_cnf(CA) ->
"authorityKeyIdentifier = keyid,issuer:always\n"
"subjectAltName = email:copy\n"
"issuerAltName = issuer:copy\n"
+ "crlDistributionPoints=@crl_section\n"
+
+ "[crl_section]\n"
+ %% intentionally invalid
+ "URI.1=http://localhost/",C#config.commonName,"/crl.pem\n"
+ "URI.2=http://localhost:",integer_to_list(C#config.crl_port),"/",C#config.commonName,"/crl.pem\n"
"\n"
"[user_cert_digital_signature_only]\n"
@@ -310,4 +444,7 @@ ca_cnf(CA) ->
"subjectKeyIdentifier = hash\n"
"authorityKeyIdentifier = keyid:always,issuer:always\n"
"subjectAltName = email:copy\n"
- "issuerAltName = issuer:copy\n"].
+ "issuerAltName = issuer:copy\n"
+ "crlDistributionPoints=@crl_section\n"
+ ].
+