2 Authentication
Authentication takes place in two phases. First, the application which is
injecting PLAN programs into the network must authenticate itself with the
node on which it requires secure access. This is done by an exchange of
messages (a protocol) which allows both parties to derive a shared
secret.
Secondly, after the authentication protocol has taken place, the user uses
the shared secret to sign a chunk (a piece of code, see below)
to be executed on the secured node. Before evaluating the chunk, the node
verifies the signature (using the derived shared secret) as that of
the purported identity of the code. If this checks out, then that code is
evaluated with the privilege of that user -- any additional services
that are allowed to the represented user are added to the default
environment, and any restricted services are subtracted. This
technique is called namespace-based security. We'll talk
about it first, and then discuss authentication.
2.1 Autheval
A chunk (or code hunk) may be thought of as a function that is
waiting to be applied. In PLAN, chunks are first-class (that is, they are
program values), and consist internally of some PLAN code (if necessary), a
function name, and a list of values to be used as arguments during the
application. A chunk is evaluated as part of normal packet evaluation, or
by passing it to the eval service, which processes the definitions in
the code, resolves the function name with the current environment, performs
the application, and returns the result.
We've added an additional service called authEval which takes as
arguments a chunk and a digital signature. The chunk is in marshalled form,
represented as a PLAN value of type blob (meaning unstructured data),
while the signature is a PLAN three-tuple of type blob ×
int × int. The first field of the three-tuple is
the cryptographic signature, the second is the user's identifying SPI
(Security Parameters Index) as negotiated in the authentication protocol,
and the final field is a counter, to prevent replay attacks. authEval
verifies the digital signature against the binary representation of the
chunk. If successful, the chunk is evaluated as in eval; otherwise,
the exception AuthenticationFailed is raised. During the evaluation
of the chunk, the authentication code keeps track of the authenticated
principal by associating it with the current thread id. When the chunk
terminates, the association is removed. This way, if any service invoked as
a result of chunk evaluation wishes to perform authorization, it may ask the
authentication service for the current principal. Because a caller's thread
id cannot be forged, this provides a safe way to track a principal without
worry that some malicious service will change the associated principal after
the authentication phase.
2.2 Authentication protocol
As mentioned, authentication is done in advance of any PLAN program exchange
through a protocol which negotiates a secret shared between a user's
application and a PLAN node. This protocol is a variant of the well-known
Diffie-Hellman [4] exchange which makes use of public key
cryptography to prevent man-in-the-middle attacks. We illustrate this
protocol with an exchange initiated by an application to a node:
-
The user application requests authentication with a remote PLAN
node. The application sends a PLAN program to the node with which it
wants to authenticate. This program invokes the PLAN service DHmessageOne with two arguments: a certificate, and a signature of
that certificate using the user's public/private key pair. In the current
implementation, we use DSA [5] keys for authentication. All
certificates used during the exchange are PLAN tuples which begin with the
following four fields:
-
the signer's public key
- a random number (a cookie)
- the time at which certificate is valid
- the time at which certificate expires
The latter three fields are are essentially used to prevent replay attacks.
Note that the duration of time fields implies the level of synchronization
between the two nodes' clocks. This first certificate additionally
contains:
-
the sender's exchange id
- the receiver's address
- the sender's address
The exchange id is generated at the sender, and is used to identify this
particular protocol exchange. At the completion of the protocol it will be
used to establish the SPI (more later). For a node, the address is
represented as a PLAN host, while an application's address is of type
host × port.
- When the node receives the message, it verifies the signature on the
certificate with the certificate signer's key. It then makes sure the
certificate is active and has not expired, and that the receiver's address
is the current node.
If the current node wishes to negotiate with the sender, it creates a bit of
state to keep track of the exchange. It stores the sender's exchange id,
calculates its own exchange id, and additionally stores the sender's address
and public key. It also calculates its local portion of the shared secret
and the ``public'' value of this secret. The response certificate includes
both exchange id's, the public value, the app's public key, and both
addresses.
Since the response message is being sent to an application, rather than a
node, it is packaged as a tuple, labeled by a string ``DHmessageTwo,''
to be delivered to the application. This tuple also contains the
certificate and its signature.
- The application verifies the signature, looks up the exchange id to
find the information stored about this exchange, and verifies that it is all
correct. It then calculates its secret and corresponding public value, then
combines it with the value in the message to produce the shared secret. The
SPI identifying the secret is then calculated based on the two exchange
id's. This SPI is used to identify the secret in later messages which have
been signed using the secret. The application's public value is included in
a message back to the node which is essentially a mirror of the message just
received. As described earlier, this third message is actually a PLAN
program that invokes the PLAN service DHmessageThree with two
arguments: the certificate containing the described information, and a
signature of that certificate using the user's public/private key pair.
- The node receives the final message and repeats the actions taken by
the application for the previous message. No response is sent; the protocol
is complete.
Each principal in the exchange now has secret known only to the other
principal. The shared secret is used to sign chunks used by authEval,
as described. This is done by a technique called HMAC-SHA1 [6]. At
this point you might be wondering, why go through all this trouble when we
can simply sign and verify chunks using a principal's public key? The
simple answer is that using HMAC-SHA1 is much faster, following the
trend that secret cryptography is faster than public key cryptography.
In our implementation, the secret is stored in two tables; one table indexed
by the peer's address (which includes other information about the protocol),
and another indexed by SPI. The former table may be used by the application
when it wants to send a message to the peer, the latter table is used to
look up the SPI found in a signed chunk so that the signature may be
verified. More about how the implementation may be used is found in the
next section.
Note that this authentication exchange is not limited to application
contacting a node---nodes may contact other nodes and applications may
contact other applications. The latter is in common practice in the
Internet today, and the former may be used, for example, to establish trust
relations between administrative domains.
2.3 Authentication Tutorial
We'll present two tutorials for authentication in PLAN, presenting first
application-node authentication, and then node-node authentication. For
these tutorials to work, you need to compile PLAN to use QCM, and to
have authentication/authorization debugging messages turned on. To do
so, edit the Makefile as follows:
-
Change the line at the top of the Makefile
# USE_QCM = true ### Use QCM as the node policy manager
to be uncommented:
USE_QCM = true ### Use QCM as the node policy manager
- Add the AUTH_DEBUG flag to the compilation flags. That is,
change
CAMLP4FLAGS = -DPLAN_UNSAFE -DPLANPORT_TCP
to be
CAMLP4FLAGS = -DPLAN_UNSAFE -DAUTH_DEBUG -DPLANPORT_TCP
After making these changes, do a make clean depend to regenerate
the dependencies, and then make to rebuild the system.
2.3.1 Application-Node Authentication
A sample application and service have been provided as templates for writing
code using the security services. This application will perform the
authentication protocol as described and then invoke a user-defined function
which may make use of the shared secret; by default this involves simply
printing a message from the authenticated node.
To try out the application, first start an active router as described in the
PLAN tutorial [3]. As in the tutorial, we'll assume that the
router is started on a machine with address m on port 3324. Using this
port is important because the router will use the keys provided with the
plan distribution in the file key.3324. We also assume that all
commands are invoked from the plan directory.
We need to start the application so it may negotiate with the router. The
application is called authapp; it may be built by doing
make authapp
which deposits the executable in the bin directory. Usage of the
program can be obtained by bin/authapp -help:
usage: bin/authapp [-hf host_file] [-p local_port] [-s socknum]
[-server] dest_host key_file
-hf Specify name resolution file (default=EXP_IP_ADDRS).
-p Set injection port. (default=None)
-server Set Server Mode (default=not set)
-s Specify domain socket to connect to on receiving side. (default=3324)
As required arguments, the application takes the hostname of the router with
which it wishes to communicate and the files that store its own public and
private keys. Keys can be generated by another program provided with the
distribution called plan_keygen, which may be built using the command
make plan_keygen. This program takes a filename argument and
generates DSA public and private keys storing a base-64 representation in
the given file.
Standard options to authapp include -hf
for the name of the
file used to resolve domain-name addresses, and -p
for the port
number of the local plan interpreter; see the tutorial document for more on
these. authapp is general enough that it may authenticate not only
with an active node, but also with another application (and may itself be
the server in that instance) or serve as the server side of a node-app
authentication. In the case that the application is authenticating with
another application, authapp would use the -s
option to specify
the PLAN port number on which the remote application is listening at the
specified destination. To use authapp as the receiving participant in
the protocol exchange (where the initiator is either another authapp
or a PLAN router), use the -server
option.
In our example, we'll assume that we're starting the authapp on the
same machine as our router, m. From another xterm, we would type
bin/authapp -p 3324
m key
where key is the file that contains the application's public and
private keys. This has been provided with the distribution (you don't
need to generate it) in the plan directory.
The application sends the first message to the node. Printed on the
application console are the following messages2:
main: got my port: <port:(127.0.0.1,3324),1>
Storing entry with key <Node: (127.0.0.1,3324) >
Essentially the application is storing in its protocol table that it has
started a negotiation with the node at (127.0.0.1,3324) (i.e., m).
Furthermore, the application is keeping track of the PLAN port it uses to
communicate with its local router; this port contains the session id for
this application (1).
In the node's log file, we find the following messages are printed after the
receipt of the first message:
Authproto: [ DHmessageOne ] with cert (<blob:566>,<blob:38>,93051
9501,930520101,0,(127.0.0.1,3324),((127.0.0.1,3324),<port:(127.0.
0.1,3324),1>))
Storing entry with key <Application: ((127.0.0.1,3324), <port:(12
7.0.0.1,3324),1>)>
This indicates that the authentication request was received and the protocol
was initiated for the application on node (127.0.0.1,3324), which has
session id 1. The node sends the response, which causes the app to print
Authproto: [ DHmessageTwo ] with cert (<blob:566>,<blob:39>,930519
505,930520105,0,0,<blob:309>,<blob:566>,((127.0.0.1,3324),<port:(1
27.0.0.1,3324),1>),(127.0.0.1,3324))
Finding entry with key <Node: (127.0.0.1,3324) >
Calculated shared secret |2125718927524411328493843923218892174119
922226269965517999906850471020703992323480943083011891723260923569
786035298658358526807900077466329250605511687325263329121557366026
123831338589432298164307582211644566693860112609214362571249075333
519598834822874461197794695987211289164577495723370914058267031267
2334|
sSPI=0
rSPI=0
Updating entry with key <Node: (127.0.0.1,3324) >
Authapp indicates it has received the message from the node that contains
the node's public value. From this and its own secret (now generated),
authapp creates the shared secret which it stores in a table. The shared
secret is printed to the console, followed by the generated sender and
receiver SPI's for the exchange (sSPI and rSPI, respectively). Finally, the
node receives the final message from the application, logging:
Authproto: [ DHmessageThree ] with cert (<blob:566>,<blob:39>,9305
19512,930520112,0,0,<blob:308>,<blob:566>,(127.0.0.1,3324),((127.0
.0.1,3324),<port:(127.0.0.1,3324),1>))
Finding entry with key <Application: ((127.0.0.1,3324), <port:(127
.0.0.1,3324),1>)>
Calculated shared secret |2125718927524411328493843923218892174119
922226269965517999906850471020703992323480943083011891723260923569
786035298658358526807900077466329250605511687325263329121557366026
123831338589432298164307582211644566693860112609214362571249075333
519598834822874461197794695987211289164577495723370914058267031267
2334|
sSPI=0
rSPI=0
Updating entry with key <Application: ((127.0.0.1,3324), <port:(12
7.0.0.1,3324),1>)>
Here we see that the shared secret and the SPI's calculated are the same.
When you run this code, the shared secret will not have the same value as
the one shown here, but the SPI's should be the same (since they all start
counting from 0). The node stores this information in its tables.
Finally, the application executes its user function. This function creates
a chunk which simply prints a message ``authenticated successfully.'' It
signs this chunk and sends it the remote node, providing the chunk and the
signature as arguments to authEval. If authentication
succeeds, the message will be printed at the application, otherwise an
exception will be raised. The application prints:
Finding entry with key <Node: (127.0.0.1,3324) >
Signing tuple |(<blob:43>,0,1)|
Data = |[8][0][0][0][3][6][0][0][0]+[13][0][4]doit[0][0][0][1][2][
0][4]doit[0][0][0][2][0][5]print[0][1][2][0][7]authSvc[0][0][1][0]
[0][0][0][1][0][0][0][1]|
Sign = |[23]x@G[237]_[246]_[251][220]x[229]|
Sending test message
In the node log, we see the message
Verifying tuple |(<blob:43>,0,1)|
Data = |[8][0][0][0][3][6][0][0][0]+[13][0][4]doit[0][0][0][1][2][
0][4]doit[0][0][0][2][0][5]print[0][1][2][0][7]authSvc[0][0][1][0]
[0][0][0][1][0][0][0][1]|
Calc |[23]x@G[237]_[246]_[251][220]x[229]| =?
Sign |[23]x@G[237]_[246]_[251][220]x[229]|
Here we see that the node verifies that the provided signature matches the
calculated one. Then, as a result, the application prints the message
127.0.0.1:3324 authenticated successfully
Authapp as a template
The authapp program was written as a template as well as a
demonstration program. It has been structured to allow easy modification
for your own purposes.
Assuming that the first thing you want your application to do is to
authenticate with a node, you need only modify the function user_main
to perform the appropriate actions. This function is of type
Authproto.principal_addr × Activehost.activehost
× (Basis.active_packet ® unit) ×
(unit ® string). The first argument is the
peer just authenticated with (either a node or an application), the second
is address of the local PLAN interpreter, the third is the function used to
send packets to the interpreter, and the fourth may be used to receive
data from the interpreter (presented as strings). When the user_main
function returns, the connection to the local interpreter is closed and the
program exits.
It is slightly more complicated if you want to authenticate with multiple
nodes and perhaps perform different actions with each of them. In this
case, you have to pull the authentication code out of main into a
separate function which may be repeated, perhaps on a per-thread basis.
2.3.2 Node-Node Authentication
The PLAN daemons are also equipped with the ability to authenticate with
other nodes; this ability is particularly important to the firewall
application that we will illustrate in Section 3.3.2.
When a node starts, it may be given a list of hostnames to its
-authlist
parameter to indicate the other nodes to authenticate with.
For each address given in the list, it waits until an entry may be found in
the RIP routing table for that node, and then initiates a connection to it.
This allows a node-node connection to be possible for any node on the
network, as opposed to just adjacent nodes (which could be achieved by
looking in the ARP table for the requested recipient). However, this
technique leverages the fact that the RIP routing implementation does not
having ``default'' routing table entries. As an alternative, we could have
simply attempted to send the initiation message, retrying if the destination
was determined to be unreachable along the way.
We present the tutorial by starting two nodes, as in the PLAN
tutorial [3], one on port 3324 and the other on port 3325. We
start the one on port 3325 first, just as in the tutorial:
% bin/pland -l log3325 -ip 3325
m2
Then we start the one on 3324, nearly the same as in the tutorial, but we
also specify m:3325
as an argument to -authlist
:
% bin/pland -l log3324 -ip 3324 -authlist
m:3325
m1
The initiating node (m:3324
) first waits for the receiving node
to become accessible:
trying to authenticate with node (127.0.0.1,3325)
currently unknown
trying to authenticate with node (127.0.0.1,3325)
Once the receiver is known, the initiation message is sent. The receiving
node processes the initiation message, calculates its portion of the shared
secret and sends the response to the initiating node:
Authproto: [ DHmessageOne ] with cert (<blob:566>,<blob:38>,930513
828,930514428,0,(127.0.0.1,3325),(127.0.0.1,3324))
Storing entry with key <Node: (127.0.0.1,3324) >
The initiating node receives the response, calculates its own portion of the
shared secret, and using the information in the message received, calculates
and stores the shared secret:
Storing entry with key <Node: (127.0.0.1,3325) >
Authproto: [ DHmessageTwo ] with cert (<blob:566>,<blob:39>,930513
832,930514432,0,0,<blob:308>,<blob:566>,(127.0.0.1,3324),(127.0.0.
1,3325))
Finding entry with key <Node: (127.0.0.1,3325) >
Calculated shared secret |2576912389757616131645963461487159443026
571643347028392844167107049555585165992701860118710028039629843832
243076311528753543566417243156174533504726150218095603906742182340
692675347036154605455462926931605367360169111711582680304704305625
472475762774815248463312144731495286620602337979104139886906037797
263|
sSPI=0
rSPI=0
Updating entry with key <Node: (127.0.0.1,3325) >
Finally, the receiving node receives the other piece of the shared secret,
and calculates and stores the full secret.
Authproto: [ DHmessageThree ] with cert (<blob:566>,<blob:39>,9305
13839,930514439,0,0,<blob:308>,<blob:566>,(127.0.0.1,3325),(127.0.
0.1,3324))
Finding entry with key <Node: (127.0.0.1,3324) >
Calculated shared secret |2576912389757616131645963461487159443026
571643347028392844167107049555585165992701860118710028039629843832
243076311528753543566417243156174533504726150218095603906742182340
692675347036154605455462926931605367360169111711582680304704305625
472475762774815248463312144731495286620602337979104139886906037797
263|
sSPI=0
rSPI=0
Updating entry with key <Node: (127.0.0.1,3324) >
Multiple protocol exchanges may occur simulataneously and will be stored
properly in the resulting tables.
2.4 Implementation Notes
The code used to implement the authentication protocol may be found in the
security directory in the PLAN distribution. The file authproto.ml contains the guts of the code that does the protocol
operations. The protocol is initiated by calling initiate_dh_exchange. Each of the exchanged messages is processed by
dh_message_one, dh_message_two, and dh_message_three, respectively, and the certificate to be sent in
response is returned. Each one of the protocol-processing functions is
called by wrappers appropriate to the principal type -- either node or
application (recall that for the former, the protocol messages take the form
of PLAN programs making service invocations, while the the latter are
delivered tuples) -- and sends the response. The PLAN services used in the
protocol are defined in authproto_svc_impl.ml. The code for the
node-node authentication may be found in nodeauth.ml.
The file auth.ml implements functions for authentication, both
DSA-based for the protocol, and HMAC-SHA1 for subsequent authentications.
It also provides services used by authEval for mapping a principal to
a thread id, and a service for retrieving this mapping, used by the
authorization functions. Autheval itself is defined with a cast of
supporting functions in crypto_svc_impl.ml. The DSA implementation
resides in the crypto directory.
The messages printed during the authentication are printed because the
compilation flag -DAUTH_DEBUG is on by default. To eliminate these
messages, recompile without this flag (remove it from the CAMLP4FLAGS
variable in the Makefile).
Finally, the protocol is computationally intensive, and it is often the case
that the initiator completes his portion of the calculation (and is thus
ready to use the secret) before the receiver. To compensate, we put a timer
in authapp to wait for the receiver to finish. You may find that you
have to bump up this timer on slower machines. A more robust solution would
be to have the receiver send a completion message.