[asterisk-dev] summary - TLS support for HTTP, AMI and more
Luigi Rizzo
rizzo at icir.org
Wed Dec 6 00:27:49 MST 2006
A few days ago i asked about certificate negotiation in asterisk.
On a related topic, i have been thinking for a while on how to provide
TLS support for HTTP, AMI and more services within asterisk, keeping
in mind the current state of affairs, the feedback i received on the
above request, and with the goal of minimizing changes to the current
code base. Now i think i am at a stage where i have no more ideas, so
attached you find:
- a description of the current situation with respect to the
implementation of TLS support for HTTP and AMI;
- a workplan on how to add TLS support to AMI,
- general considerations on how to provide TLS TCP sockets.
Please let me know if you have better ideas on the above, but
be careful that "the devil is in the details" (e.g. we still
needs a poll()-able fd for the TLS sockets), and that we are
a bit constrained by backward compatibility issues so e.g.
changing internal interfaces such as the one for CLI commands
is not really feasible without widespread changes to the code.
Note that the relevant code (currently in main/http.c, possibly to
be relocated elsewhere) is designed to be as independent as possible
from the specific service. So its functionalities can be reused
(hopefully without modifications) wherever we need TLS TCP sockets.
Feedback welcome.
cheers
luigi
--- CURRENT STATE - AMI ---
As it is now, the AMI code does I/O on the level-2 file
descriptor returned by accept() (or created in generic_http_callback()
in the case of AMI-over-HTTP).
On the input side, the code loops around the function get_input()
interpreting a returns of 1 as "full line available" and
0 as "possibly an interrupt arrived".
On the output side, with only one exception, all I/O is performed
in the function send_string(), called by either astman_append(),
or by (internally) by process_events(). The routine tries to write
a buffer to the file descriptor with a bounded timeout. (in fact,
the way it is written, ast_carefulwrite() does not give any guarantee).
In order for the above to work, the socket must be in non-blocking
mode (block-sockets=false in manager.conf).
The exception, on the output side, is the function action_command(),
which runs a CLI command over AMI. In this case the file descriptor
is passed directly to ast_cli_command() so there is no control
on the way I/O is performed.
The input part loops around get_input() to read one line at a
time from the socketinput() to read one line at a
time from the socket. get_input() is also expected to return
0 (and an empty buffer) when a signal is sent to the thread,
typically because there is a new event to be processed.
--- CURRENT STATE - HTTP ---
The code in main/http.c has full https support. This is relatively
straightforward because all I/O is done
using a FILE * descriptor. The latter is obtained by just using
fdopen() on the socket returned by accept() in case of plain http
requests, or by using funopen() or fopencookie() (depending on
which one is available) to install handlers to encrypt/decrypt the data.
The routines to set up the tls session are generalized in a way
that they can be made globally visible and used by other modules.
--- ADDING TLS SUPPORT TO AMI ---
In my opinion the way to go for adding TLS support to the manager interface
is the same used for http, i.e.
1. change the code to use a FILE * instead of a level-2 file descriptor
2. on setup, call the function ssl_setup() (from main/http.c) to
establish the encrypted connection.
#2 is trivial - it basically requires to change from static to
globally visible the TLS/SSL support functions/variables currently
in http.c
#1 is mostly trivial too, and requires the following steps.
a) after the accept, make sure that make_file_from_fd() (from http.c)
is called on the socket to create a proper FILE * for plain
or encrypted sessions;
b) change the function get_input() to use fread() instead of read()
to collect the data. One can still do the ast_wait_for_input() on
the original descriptor returned by accept().
c) change the function send_string() to work on the FILE *.
This is also relatively straightforward, especially given that
a rewrite is necessary anyways because ast_carefulwrite() does
not give the guarantees we want.
d) modify the function action_command() so that it creates a
temporary file descriptor to be passed to ast_cli_command(),
and then read back the data from the temp file and write it
to the output with send_string(). The code is similar to
what is done in generic_http_callback() to support AMI-over-HTTP.
--- GENERIC TLS TCP SOCKETS ---
Other applications needing (server) TLS sockets should likely be able to
reuse most of the functions in main/http.c, namely
server_start() and server_root(), which take charge of killing
any old instance of the service on the same port,
creating a new thread in charge of doing the accept(), and then
creating new threads in charge of the certificate negotiation and
I/O for the new session.
For client TLS sockets, there is not much support, but all
should be needed after the conventional socket()/[bind()/]connect()
is just a call to make_file_from_fd() to set up a proper FILE *.
We can provide a wrapper for the above if there is a need
(i don't know if any other modules currently use client TLS sockets).
--- SERVER CERTIFICATE ---
At the moment, the code that reads the server's certificate is
within function ssl_setup(), and the code above is written assuming
only one certificate for the whole server, accessible from the
variable ssl_ctx. It is not difficult to let each service use a
different certificate. As pointed out by Klaus Darilion,
let the server pick the certificate depending on the request
(useful for the equivalent of 'virtual domains') requires some
additional features ("server name" TLS extension) which i
don't know how to implement.
--- THE END ---
More information about the asterisk-dev
mailing list