The free name function is denoted fn( ). The notation ~f denotes zero or more occurrences of f. The term Pft=xg represent process P in which all free occurrences ...
Coordinating Agents with Secure Spaces Jan Vitek
Ciar an Bryce
Manuel Oriol
Abstract
Linda and its relatives in the familly of coordination languages provide a great deal of
exibility for developing parallel and distributed applications but lack security mechanisms. Security is needed whenever programs operating at dierent level of trust are granted access to a shared resource. This paper investigates the design of a coordination model, secure spaces, with built-in protection mechanisms for ne grained control over shared memory. We show that our model is expressive enough that Linda programs can be translated into our language. Feasibility is demonstrated with a Java implementation of the model called SecOS. We also discuss an alternative implementation that relies on cryptographic primitives to enforce access control in untrusted environments.
1 Introduction Assembling software systems out of components | in particular, independently developed ones | remains an open problem in software engineering. But even more challenging is the composition of partially trusted and mutually mistrusting components. In this paper we propose a coordination language based on Gelernter's generative communication [12] as the main interaction mechanism in a mobile agent system. Mobile agents are migrant programs in a network of dedicated hosts. They are neither particularly trusted nor particularly trustworthy; every one of their actions must be controlled by their host to protect other agents and the host itself. The main question in the design of an agent system is `How do agents interact?' or equivalently `What kind of communication primitives does the host provide?' The dilemma is to be exible so that agents from dierent sources may collaborate and to be secure which typically implies limiting exibility in favor of execution guarantees. Generative communication provides the exibility. This paper shows how to get security without crippling the model. Linda, Gelernter's original coordination language, was designed and implemented for parallel machines and small scale distributed systems. It features pattern-matching communication primitives de ned over a shared memory, called a tuple space, that exibly associate output oers with input requests allowing unrelated processes to synchronize and communicate. Although quite general, Linda has mostly been used for scienti c computing. Accordingly the main trust of the research eort has been directed at improving runtime performance for cooperating groups of processes. This explains why security was not considered in the initial design. But without protection mechanisms the integrity and privacy of objects living in tuple space can not be guaranteed. A malicious application can, for example, wreak havoc on an entire Linda system by indiscriminately removing objects from the shared memory and thus starving other processes. The model does not help dierentiating an honest input request from a malicious one. Linda primitives can just as easily be used for spying and spoo ng on other applications using the same space. To make matters worse, denial of service attacks are trivially mounted by ooding a space with requests. Since objects persist in shared space, any loop that outputs data that is not matched up with a reader will eventually cause the space to run out memory. This may not even be caused by a 1
malicious process, a programming error or the failure of a consumer are sucient. The problem with
ood attacks is that by de nition there is no reachability information to automatically determine if a particular object is accessible or if it is garbage. The challenge we are faced with is how to modify Linda to be able to oer security guarantees to mutually untrusting programs. This goal is constrained by the desire to retain the open character of Linda's associative communication primitives and also constrained by the need for ecient implementations. With the exception of KLAIM and JavaSpaces which are discussed later, we are not aware of any work in this direction. Our approach is to de ne a very simple coordination model, which we refer to as secure spaces. Secure spaces provide a simple locking mechanisms for controlling access to data stored in shared memory. The most basic kind of locks, symmetric locks or slocks, guarantee the privacy and integrity of secure space objects. To be more concrete, an object containing two slocked elds is denoted by h
x : joe y : pwd
i
The identi ers on the left of the column stand for symmetric keys and the symbols on the right for the locked values. We give precise semantics to secure spaces by embedding them in a small concurrent programming language. This semantics ensures that only a process that holds a key can unlock a eld. For privacy all other locked elds are hidden. Privacy also imposes changes to the pattern-matching rules used in communication. To ensure that a process does not learn about values of elds or even the presence of locked elds, the matching is extended with a subsumption rule that says that only locked elds speci ed in the query are considered in the match. Sometimes slocks are not sucient. A good example is when an agent needs to set up a communication channel with an other agent without knowing that agent or fully trusting the environment (Section 3.4). We introduce asymmetric locks, or alocks, that require a dierent key for locking and unlocking. Secure spaces are expressive: in Section 3.5 we demonstrate how to translate Linda programs into secure spaces. In Section 2.3 we show how to implement memory management without having to place the collector in the trusted computing base. A garbage collector must know only what it needs for reclaiming memory and should not learn privileged information. In Section 3.2 we show how to build higher level access control mechanisms. We introduce object locks or olocks which control the matching process and can prevent an unauthorized process from removing an olocked object from secure space. Thus with a little more syntax, an example of an olocked object is: h
x : joe y : pwd @z i
The key z locks the whole object and only processes that hold that key may retrieve the object from secure space. Secure spaces have been implemented in Java, this work is the topic of Section 4. The implementation is called SecOS, it is rather ecient and has been used in the JavaSeal mobile agent kernel. While the standard implementation assumes a trusted kernel, there are cases when secure space objects have to be exchanged over insecure communication channels or stored on disk. For these cases we investigate the use of software encryption in Section 5.
2 Some Principles of Coordination This section describes the salient features of Linda and explicits the assumptions we make about the problem domain. 2
2.1 Generative Communication
Linda is foremost implementation of the generative communication paradigm. Its rst incarnation was a full-featured distributed programming language [12]. Later Carriero and Gelernter distilled the model down to a few basic operations and packaged these in language neutral libraries [13]. The result is a remarkably elegant programming model for parallel and distributed applications which has been embedded into many languages [8, 21, 20, 18, 22]. Linda consists of only four operations, out, eval, rd and in, de ned over a shared memory called a tuple space. (Two predicate forms, inp and rdp, are not considered here.) A succinct description of these operations (taking liberties with syntax) is given here: outhtuplei
Non-blocking write of tuple in tuple space. Spawn a new process to evaluate exp and deposit the result in tuple space. Find a tuple matching tuple or block. If several candidates are found, one is chosen nondeterministically. The match is removed from tuple space and bound to x. As above except that the match is not removed from tuple space. h
evalhexpi
i
h
inhtuplei,x
rdhtuplei,x
h
i
i
The matching between input requests and output oers sets Linda apart from other parallel programming models. An output oer deposits an ordered sequences of values in tuple space, an example oer is out x,12,"joe" . This value can be accessed by any matching input request. Input requests specify templates that describe fully or partially the tuple to be retrieved. For example the fully speci ed template in x,12,"joe" ,y matches a single object. A partially speci ed template uses wildcards, written ? here, to describe the shape of the tuple being requested. An example is rd ?,12,? ,y which request any triple with second eld 12. The tuple space is thus an associative memory which is particularly useful for tasks such as resource discovery where clients must be matched up with servers based on their respective oers [25], for event-driven programming styles [22] and dynamic con guration of running systems [19]. The literature is replete with Linda variants. We mention only multiple tuple spaces as they have some bearing on the discussion. Adding an operation to dynamically create new spaces and tuple space names that can be used as values allows Linda programs to explicitly manage tuple spaces [7, 13, 17, 24]. This in turn can be used to structure communication, for example creating zones restricted to a subset of the processes. Not only do multiple space reduce the risk of programming errors, but they also provide some degree of privacy. For instance one may establish a private channel with the following two operations: h
i
h
h
new tsc; outh tsc
i
i
i
in current;
This creates a new tuple space tsc and outputs its name in the current space. The intent is that the process that does a matching in will share a private channel with the sender. Unfortunately the protocol is broken as the name can be rded by multiple processes. We will return to this issue later and argue that multiple space are not sucient for security.
2.2 Communications Security
If all components making up a software system are not equally trusted, protective measure have to be taken. Security begins with the task of identifying which resources or information is to be 3
protected and the most likely source of attacks. Consider the following streaming protocol (again taking some liberties with concrete syntax).
ProcA
ProcB in ?,? ,x;
outhj, ii;
h
i
i = x[2]; while( i -- ) inhx[1],i, ...
while( i -- ) outhj,i,v[i]i;
? ,y; i
The goal of the protocol is to transfer an array v in reverse order. The protocol starts with a header message that contains a unique protocol identi er, j, and the length of the data stream, i. The receiver matches the tuple, x[2] is used to initialize the loop counter and x[1] in the template used to retrieve data (i is also used in the template to guarantee ordering). Here the tuple space acts as a buer between the two processes. What properties of this simple protocol can be relevant to security? In practice some combination of the following: Authenticity: The sender (resp. receiver) may require that its partner be a particular process. Thus both parties may have to be authenticated. Privacy: The information transmitted may be sensitive. The value v[i] may have to be hidden from processes other than the designated receiver. Even more, the length of the stream may carry information and need to be protected as well. Integrity: No process other than sender and receiver should be allowed to interfere with the protocol. This means there should be no destructive reads or output of false messages by third parties. Linda does not provide support for any of these properties. Even multiple tuple spaces { if viewed as secure communication channels { fall short on two counts. Firstly, as remarked above, there is no secure way to set up a shared space between a number of processes. Secondly, they can not protect individual elds of a tuple, thus multiple space only provide coarse grained protection (the next section shows why that is not enough). If Linda must be extended with protection mechanisms the question is which ones? There is a tension between directly integrating security policies to enforce a mix of those properties or to provide some low level protection mechanisms that can be used as building blocks. We follow the second path as it leads to a simpler trusted computing base and does not freeze policy decisions in the design. The challenge is then to nd the least extension that suces to write secure applications.
2.3 Shared Space Management
How do we manage memory in a tuple space? This question is particularly important for long lived systems as any memory \leak" will persist in tuple space. The problem is that the nature of generative communication seems to be incompatible with automatic memory management. There is no liveness information that can help determine if a particular tuple is reachable. For instance the tuple 32,15 may be reached from in x,? ,y if the x evaluates to 32. There is no way to decide a priori if the tuple is reachable or not. Does this mean that nothing can be done? Not quite. The lack of generic memory reclamation does not preclude application speci c policies. These policies can be tied to the entire tuple space, h
i
h
i
4
to individual processes, or even to runs of a protocol. For example one can associate a time to live with tuples and have a daemon process remove elapsed tuples at regular intervals. Other policies include cleaning up after process termination or protocol completion. The issues here are how to encode the garbage collection information and where to put the collector processes. The garbage collection information should be available to the collector but not to other processes. This because applications should not depend on particular reclamation algorithm, but also to avoid programs tricking the garbage collector by insert false liveliness information. The other point is equally important, since we are arguing for application speci c policies, we expect the code to be written by users. This means the code can not be part of the kernel for security reasons and therefore that it has to run as a normal process. This creates a dilemma. On the one hand we are trying to protect the data stored in the tuple space. On the other hand, the garbage collector is a user process with access to the entire shared memory. What is needed is ne grained access control mechanisms that strike a compromise between these con icting requirements. As a point in the design space, in Sun's JavaSpaces every object deposited in a JavaSpace is associated a lease. The lease determines the life span of that object. This policy is hardwired in the implementation and does not address the issue of setting reasonable limits on how many objects can be deposited by an application. Furthermore it is quite likely that dierent applications will require dierent leases, for some several seconds may be sucient while other may need to store data for weeks or even longer. Of course, the implementation of the collector is squarely within the trusted computing base. Multiple spaces do not help in this case, in fact they add an extra diculty: they must themselves be collected.
2.4 Agents, Coordination and Trust
Mobile agent systems take a radical approach to distributed programming. Instead of hiding locality and making distribution transparent, mobile agents promote locations as the essential characteristic of distributed programming. Agents are programs that can move from site to site as part of their computation. Instead of distributed communication primitives, mobile agent languages provide a moveTo operation to migrate a running process complete with data and code to a dierent host. This shift comes from the growing importance of wide area networks and the need to support large scale distributed systems with highly variable latencies, insecure communication, administrative domains that partition the network into enclaves of trust, and malicious hosts as well as Trojan horses. The reader is referred to Cardelli [6] for more details on mobility and to Waldo et al. [?] for a critique of transparency. The reference model for agents is that an agent system consists of set of places, each of which is an execution environment capable of running multiple agents, Figure 1. An agent is a multithreaded program which can interact with the place and with other co-located agents. The key issue is how to structure and regulate communication between agents. Our experience with JavaSeal has shown that message passing is cumbersome [5]. An agent that arrives in a foreign environment might not know the naming conventions of that environment; it is more convenient for that agent to specify the attributes of the resources that it needs rather than actual names { hence the utility of generative communication. Further, since agents might migrate in the midst of a protocol, communication must be uncoupled. Furthermore as only the agents located on the same host are allowed to interact, the primitives can be local to a host. A non-distributed variant of Linda would t the bill. As it allows agents that do not know each other to communicate and change partners in case one of the parties migrates. What Linda lacks is the ability the properties outlined in the previous section: authenticity, privacy and integrity. Furthermore, in an agent system, the host 5
must be able to exert some control on which agents are allowed to communicate. Support for restricting communication abilities must likely be included in the communication primitives. Agents TCB
comm infrastructure
EE
Figure 1: A single place executes multiple agents. In the proposed model, all agents communicate locally with the place and each other using a shared space. The communication infrastructure and the implementation of the place is part of the trusted computing base.
Who can an agent trust? is a key question for agent programmers. Any part of a system (the place, other agents) that can be tampered with represents a security risk. For this reason the trusted computing base on which agents systems are built must be well identi ed and tamper-proof as it is the only thing that the agent can trust. Typically, it is preferable to keep the trusted computing base as small as possible to be able to certify correctness of the implementation. Our goal is to only include in the trusted computing base the implementation of the agent language and the basic communication primitives.
3 Coordination with Secure Spaces Secure spaces extend generative communication with a ne grained access control model. This section introduces secure spaces in the simplest of settings. We retain only two of Linda's operations in and out and embed them in a minimal concurrent language. The purpose is to strip down the programming model to its essentials { subsequent sections demonstrate how to regain Linda semantics { in order to understand the implications of our modi cations to these primitives. We now introduce the main features secure space coordination model. The syntax and semantics are detailed in the following sections. Secure spaces are used to communicate objects between processes. Unlike Linda's tuples which ordered sequences of values, secure space objects are unordered sequences of locked elds, each eld consisting of a key and a value. There are two kinds of locks, the simplest are symmetric locks or slocks. A Linda tuple such as 3 "joe" may be protected by locking both elds:1 h
i
a : 3 b : "joe"
h
i
The identi ers on the left of the column are symmetric keys, while the symbols on the right denote values. Slocks owe their name to the fact that the same key is used to lock a eld and to unlock it. To ensure privacy, a process that does not know a key should not learn the value associated, but even more a process should not be able to learn how many keyed elds or which keys were used. All these elds are considered hidden. We will see shortly that hiding elds impacts matching. Keys are values in our model. Fresh keys can be generated, the new operation is written (new a) and it creates an unguessable key a. Revealing a little more syntax, the key a is known to only one We take the liberty of using integers and strings in our examples. Extending the language with these data types is straightforward but does not bring any insights. 1
6
of the two following parallel processes.
P j (new a)Q The language is lexically scoped, thus we deduce that P does not know a from the fact that it is
not within the scope of the key. The following creates a new key and uses it to create the slock guarding eld value x. (new a) out a : x Since none else can guess the key we have eectively locked x and thrown away the key. To access the value of a locked eld, a key matching the slock must be presented, in the case of slock a : b the matching key is a. The following is a selection expression that extract value 3 from an object. a : 3; b : "joe" :a Pattern matching must be modi ed to mind hidden elds. To prevent a requestor to learn anything about the hidden elds of the objects it is matching against we add a subsumption rule to the match procedure. The subsumption rule says in essence that a longer object can be matched with a shorter template. Hidden elds simply do not take part in the matching. Furthermore, we choose to disregard the order in which elds occur. We simply require that each lock occurring in an object be unique. With this restriction object equality is thus simply a comparison of lock/value pairs. Matching uses the structural subsumption rule: an object with more elds matches a smaller object if the smaller object's elds can matched pairwise with elds in the larger object. The unmatched elds of the latter are considered hidden. Two elds match if (a) their keys match and (b) their values are equal or if the value of one them is the wildcard. Thus an output oer out a : b; d : e can be matched by the following input request in d : e x : 0. Other matching templates are a : b , a : ? , d : ? , a : b; d : ? ; a : b; d : e and . The empty object can be used as a template to match any object. It is important to recall that retrieving an object does not grant access to its elds. Without the appropriate keys, elds remain hidden. With these primitives we can implement a simple key exchange protocol, with a shared key a: ? (new b) out a : b j P j in a : ? x : Q which reduces in one step to ? (new b) P j Q a : b =x Since both processes now share b, its scope extends to enclose P and Q. Furthermore, all occurrences of x in process Q have been substituted with the object a : b , e.g. x:a is now a : b :a which evaluate to b. If Q does not know a then the eld's value remains unaccessible. Asymmetric locks are pairs of keys, ab and ba such that if ab locks a eld then only ba can unlock it. One use of asymmetric locks is to give ab as a public key and keep ba as a private key. In this case key exchange becomes: ? (new c) out ab : c j P j in ab : ? x : Q The new key c is sent locked under Q's public key. To access the locked eld Q would write x:ba . Note the asymmetry between pattern matching and eld access. To retrieve an object Q uses the public key ab while to access the value its the private key ba . h
h
i
i
h
i
h
h
i
h
i
h
i h
i
i
h
hi
h
hi
i
h
f
h
i
h
h
i
i
g
i
h
7
h
i
i
i
The last feature of secure space is that objects can be extended without revealing their contents. The following expression is an extension
a : 22
h
i
b : "joe"
a : 22 b : "joe"
yields
h
i
The process doing the extension knows nothing about the contents of the object it is extending, but it must have an extension key. If the extension key is already present in the object the new value overrides the original one. In the case of alock extension is done with the public key.
ab : 22
h
i
ab : "joe"
ab : "joe"
yields
h
i
Up to now we have controlled access to elds, but not the pattern matching process. It is desirable to prevent processes using the empty template to match indiscriminately. Object locks restrict the visibility of entire objects from the pattern matching process. A locked object is created hi
out a : 12 bc : 3 @d h
i
where key d is used to lock the entire object and a matching input is in bc : ? x@d : P . Object locks are related to multiple tuple spaces as they partition the shared memory. But object locks, as we will show, are built out of simple slocks and do not need special attention for memory management. We conclude with some remarks. The core language is based on the asynchronous -calculus [16, 4, 3] which is known to be very expressive. To we added objects and matching. It is straightforward to encode in our language. The calculus is untyped, which allows certain ill-formed processes to be written, type errors cause processes to get stuck and prevent further reduction. By introducing locked elds we abandon the positional notation of Linda. Locks add structure and avoid relying on unwritten agreements on the meaning and use of dierent position in tuples. While positional notation is easy to use, it is also error prone (two tuples of the same arity can easily get mixed up) and does not lend itself to extensions. Labels make the agreement explicit (it is necessary to agree on names before communicating) and help in making code more robust [10]. Although we are interested in coordinating mobile agents, the language presented here does not allow the exchange of code or processes. A treatment of mobility can be found in [26, 6]. The calculi could be merged at the cost of some complexity, but we prefer to leave this as a topic of future investigation. We start with a core language with symmetric locks, asymmetric locks and object locks will be presented later. h
i
3.1 The Core Language
The syntax of the core calculus is summarized in Table 1. We take an in nite set of names ranged over by meta-variables a; b; c; : : : (except e; k; t; v). We take a set of locks, ranged over by `. For now we let = (see Section 3.4 for a richer notion of locks) and de ne a co-lock ? . Basic values are ranged over by v; ? function as ` = `. The set V of basic values is is the distinguished void element. Objects belong to the set , ranged over by t. A secure space object is a, possibly empty, sequence of elds `1 : v1 : : : `i : vi with distinct labels `1 : : : `n . Fields are lock-value pairs, we use the notation f~ for a possible empty sequence of elds. The function locks (t) yields the set of locks in object t. The syntactic category of expressions , ranged over by e, includes basic values, objects, selection expressions and extension expressions. The syntactic category of processes , ranged over by P; Q, includes the empty process which has no behavior, parallel composition of processes as well as process creation. There are two communication primitives: in e a : P for input and out e for output. N
L
L
N
L [ f g
T
h
i
8
in e a P tries to extract an object matching the template e and binds the result to name a. The operation is blocking, P cannot execute until the match succeeds. out e outputs the object denoted by e. The restriction operator (new a) introduces a fresh name. In our case we also view it as key generation; (new a)P means that a is valid only in P . We use alpha conversions of bound names :
in expression evaluation. The free name function is denoted fn ( ). The notation f~ denotes zero or more occurrences of f . The term P t=x represent process P in which all free occurrences of x are replace by t. Trailing inert processes are elided; thus in t x@e : 0 becomes in t x@e. The operational semantics of the calculus is given in Table 2. The reduction relation P P indicates that P reduces in one step of internal computation to P . We de ne two auxiliary notions: structural congruence and evaluation . Structural congruence is the least congruence on processes satisfying the axioms and rules given in Table 2. The evaluation relation yields the result of eld selection and object extension expressions. The reduction relation is the least relation on processes that satis es the axioms and rules de ned in Table 2. We now review some of the key rules of the semantics. The main reduction rule determines the conditions for matching an output oer with an input request. If the output expression evaluates to an object t, the input to t , and the objects match then the output oer is consumed and the continuation P can execute. f
g
!
0
0
#
!
0
e t e t t t out e j in e b : P P t=b 0
#
0
#
0
0
!
f
g
The interesting rules of the evaluation relation are selection and extension. Selection extract a value from an object. This requires that e evaluates to an object, e to a key ` and that a matching key be present in the object. There is no rule for the case in which the key is not present. This is considered an error and the execution gets stuck. 0
` : v f~ e ` e:e v The extension rule adds a eld to an object. Expression e should evaluate to an object, e to a key and e to a value. We write f~=` to denote the sequence of elds in which ` does not occur as a lock. Any previous value of ` is overridden. e t t f~ e ` e v e e : e ` : v (f~=`) e t t #
h
0
i
0
#
#
0
00
#
h
0
0
i
00
00
#
# h
#
i
The subsumption rule is de ned on objects and basic values. For the latter it is a re exive relation with least element ?. For objects the rules specify that the empty object matches any object t. Furthermore, two arbitrary objects t and t are related if the elds of t can be pairwise compared to the elds of t and for each eld ` : v in t there is a eld ` : v and v v. Note the hi
0
0
e ::= v f ::= ` : v P ::= 0
0
j
j
` P jQ
j
j
t
!P
e:e
j
in e a P :
j
j
j
e e
out e
Table 1: Core Language Syntax. 9
0
j
(new a)P
Reduction P (new a)P
! !
P Q (new a)Q P jR QjR e t e t t t out e j in e b : P P t=b Q
P P P Q P Q
!
!
0
#
0
#
0
0
0
0
!
!
!
f
g
Evaluation e1 `1 : : : en `n e1 v1 : : : en vn e 1 : e1 : : : e n : e n ` 1 : v 1 : : : ` n : v n e t t ` : v f~ e ` e:e v e t t f~ e ` e v e e : e ` : v (f~=`)
v v
#
#
hi # hi
0
0
h
#
h
i
0
#
h
0
00
0
#
i # h 0
#
i
#
#
i
0
0
#
00
#
# h
#
i
Structural Congruence Rules f~ ` : v f~
h
0
` : v f~ f~
i h
0
P j0 P
P jQ QjP
i
(new a)(new b)P
(P j Q) j R P j (Q j R)
!P P !P
j
(new b)(new a)P
(new a)(P j Q) P j (new a)Q if a fn (P )
62
Pattern Matching Rules ? v
v v
t
` 1 : v1 : : : ` n : vn t
h
i
0
hi
t
`1 : v1 : : : `n : vn f~ v1 v1 : : : vn vn t t
h
0
0
i
0
0
0
Table 2: Operational Semantics. contravariance between locks and values. t `1 : v1 : : : `n : vn t `1 : v1 : : : `n : vn f~ v1 v1 : : : vn vn h
i
0
h
0
t t
0
i
0
0
0
Reduction does not proceed for nonsensical terms. While these terms can be avoided by the introduction of a type system, adding one would be of little bene t for the present discussion. 10
While a more detailed study of equivalences is beyond the scope of this work we postulate that some secrecy properties hold in our calculus. For instance, the value of locked elds are protected. Thus for any context C [] one may wish to prove that C [(new x)out x : y f~ ] and C [(new x)out x : z f~ ] are bisimilar if x fn (f~). That is to say that the value of locked elds may not be observed without the matching key. On the other hand the following terms are not equivalent C [(new x)out x : y f~ ] and C [oute f~ ] This because we can use matching to encode an equality test as shown bellow t == t : P = out t j in t y : (out t j in t y : P ) The term reduces to P only if t t . h
i
h
i
62
h
i
0
h
0
i
0
0
3.2 Locking Objects
Object locks control access to objects in secure spaces. The syntax for outputting an object e locked by e is out e@e and for reading an object matching e locked under e is in e x@e : P . The semantics is that out t@` j in t x@` : P reduces only if ` = ` and t t. The keys used to lock objects are plain keys and can be exchanged as usual. Locked object can serve as secure channels, thus in the following process ? (new a) out t@a j in y@a : P j Q Q can not interfere with or spy on the communication, and P will always get t. Object locks can be expressed in the core language. Table 3 gives an inductive de nition of an encoding of object locks. The term P is translated into the core language term [ P ] L`S where we have chosen a key ` that does not occur free in P . The translation simply extends all inputs in t x@a and outputs out t@a with the lock ` : a. The matching rules ensure that a template with a lock ` : a will only match an object containing the same lock. 0
0
0
0
0
0
0
00
hi
S
S
S
[ P j Q ] L`S [ ! P ] L`S [ 0 ] L`S [ out e1 : e1 : : : en : en @e ] L`S [ in e1 : e1 : : : en : en a@e : P ] L`S [ (new a)P ] L`S h
0
0
0
h
0
i
i
= [ P ] L`S j [ Q ] L`S = ! [ P ] L`S = 0 = out ` : e e1 : e1 : : : en : en = in ` : e e1 : e1 : : : en : en a : [ P ] L`S = (new a)[[ P ] L`S h
h
0
0
S
0
S
0
i
i
Table 3: Translation from object locks to core calculus.
The translation of the process [ out e : e @a j in y@a : 0 ] L`S yields out ` : a e : e j in ` : a y : 0 The translation assumes that all input terms be of the form in e a@e. If this is not the case then [ P ] L`S is unde ned. We postulate that [ P j (new a)out t@a ] L`S is equivalent to [ P ] L`S for all P . That is, a locked object is protected from processes that do not have its key. h
0
i
hi
h
11
0
S
i
h
S
i
3.3 Memory Management for Secure Spaces
Designing memory management schemes for associative memory implies that the code implementing the algorithm must be trustworthy enough to be granted in access to the associative memory. But by the principle of least privilege it should not acquire information that is not germane to its task. Furthermore the collector must be non-intrusive; it must run in parallel with applications and mark objects that are due for reclamation without interfering with the application logic. The core language support all of the above requirements as it is possible to de ne a collector that applies a hidden mark to arbitrary objects and concurrently retrieves expired objects. Marking is modeled by extending object with a slock consisting of a shared key `GC and a value y which can be a time stamp. Marking can be performed without gaining any knowledge about the contents of the marked objects. The de nition of mark, shown in Figure 4 takes an object and a stamp and outputs a marked object. Mark is polymorphic with respect to the locks present in the object. It does not presume the presence of any data nor can it modify any application data. The only side eect of mark is that an existing slock keyed with ` will be overwritten with the new stamp. The collector is parametric on the the stamp. It use partial matching to retrieve all entries which have been previously marked.
out t `GC : y !in ` : y x
mark (t y) = collect (`; y) =
h
i
Table 4: Marking and collection. The combination of mark and collect is exible. Multiple marks can be applied to the same object without risk of interference. Thus dierent garbage collection policies may be composed. Two example policies are revocation and protocol hygiene. An application that exceeds it space allotment (it maybe mounting a denial of service attack) or performs an illicit operation may have to be terminated. As a policy decision this can entail revocation of all pending outputs of that process. To reclaim all of the objects that are still in shared space it is necessary to be able to dierentiate between applications and nd which outputs belong to the program that is to be terminated. The task is complicated by the presence of structural congruence rules which preclude simple syntactic arguments. A promising approach is to take unmodi ed programs and add garbage collection information by translation. We de ne a translation scheme that marks each application with a dierent key. Thus in the case we have two processes P and Q, their marking [ P ] p j [ Q ] q is de ned in Figure 4, we assume that p, q and ` do not occur free in P j Q. Revocation of a one of the application, say for instance P , boils down to invoking the collector with that application's marker, collect (` ; p). Another type of reclamation policy is under direct control of the application. Processes interact by exchanging sequences of messages according to some protocol. A desirable property of a protocol is that once it completes, no outstanding objects remain in shared space. We call this property protocol hygiene. The diculty here is that there are many scenarios in which this property fails and it is dicult prevent these occurrences. For instance, one of the communication partners may be faulty or be revoked in the middle of the protocol leaving a number of unconsumed message in shared space; a process may intentionally drop part of a stream of events; and last but not least, well known denial of service attack rely on repeatedly initiating new instances of a protocol with a server until that server is overloaded. One technique for ensuring hygiene is to rely on a post hoc clean up scheme. The idea is that P
P
12
[ P j Q] x [ !P ] x [ 0] x [ out e ] x [ in e y : P ] x [ (new a)P ] x
= [ P ] x j [ Q] x = ![ P ] x = 0 = out e ` : x = in e y:[ P ] x = (new a)[[ P ] x
P
Table 5: Process marking. each participant in a protocol may apply a hidden mark to all of its outputs. A clean up procedure is invoked if the protocol has to be aborted early or to eliminate any stray messages. A hygienic protocol is declared with newprot x, where x is a fresh protocol name. Then all messages that are part of the protocol, the sender would simply brand as out ex . Finally the protocol is closed by endprot x. Table 6 gives a translation of the hygienic primitives to the core calculus. The primitives only mark objects that are declared part of the protocol. The end of the protocol triggers a collection. Note that the translation has been set up to prevent a process from calling the collector with an arbitrary name. It enforces static scoping of protocol names. An example of a small protocol is (newprot a)
?
out ` : x a in ` : ? y endprot a h
j
i
h
:
i
and it translates to (new a)
?
out a : ? ` : x in ` : ? y h
[ P j Q ] HS [ ! P ] HS [ 0 ] HS [ out e ] HS [ out ex ] HS [ in e y : P ] HS [ (new a)P ] HS [ (newprot a)P ] HS [ (endprot a) ] HS
i j
= = = = = = = = =
h
i
:
collect (a; ?)
[ P ] HS j [ Q ] HS ! [ P ] HS
0 out e out e x : ? x S in e y:[ P ] HS (new a)[[ P ] HS (new a)[[ P ] HS a collect (a; ?) a S
Table 6: Hygienic protocols.
2
[f g
2
The interesting point about these marking schemes and the associated collectors is that they are compositional. Several collection algorithms may run in the same secure space without application noticing. The collectors are trusted to the point of being able to reclaim objects but no more, in particular they can not glean information about the data contained in the objects they collect. 13
N
B
A
Figure 2: Setting up a secure channel between A and B.
3.4 Asymmetric locks
Asymmetric locks, or alocks are part of the core language, but we deferred their introduction for clarity of the exposition. Asymmetric locks rely on matching pairs of keys, written ab and ba . The only changes to the core calculus are that the domain of locks is extended with values of the form ab and that the co-lock function returns the inverse of a key, thus ab = ba . The matching and extension rules of Table 2 still requires key equality while extraction use the co-lock. To demonstrate the usefulness of alocks we give the example of key exchange protocol based on the wide mouthed frog protocol. Assume that agent A knows the public bp of B and also shares a secret key s with a trusted name server N . A desires to interact with B in a secure fashion as summarized in Figure 2. The protocol proceeds as follows rst A sends a message to N L
(new a)out bp : a @s h
i
The object contains a new key a locked under B 's public key. The whole object is locked under the shared key s. The name server forwards the message under s which is a secret key shared with B . 0
in x@s out x@s :
hi
0
Process B receives the message, it can use its key to extract a and use that value as lock for communication with A.
in x@s out @(x:pb ) hi
0
:
hi
This protocol is quite safe. The only potential threat here comes from a malicious name server and even it is limited to what it can do. It can withhold or duplicate messages. The latter can be detected by using nonces. But most importantly, the asymmetric keys give us the assurance that the server can not nd out a. So if A and B do establish a communication channel they can be sure that the channel is secure. Asymmetric locks are handy here as they allow B to publish bp widely yet the values locked under it remain protected.
3.5 Linda with Secure Spaces
We show that traditional Linda programming styles can be used as well with secure spaces. eval is naturally available in the calculus with the parallel composition operator P j Q. rd is easily encoded as an input followed by an output in parallel with the continuation process
rd e x P = in e x ?P out x :
:
14
j
The last issue is how to recover positional notation. Let's assume a set of keys `1 ; `2 ; `3 ; : : : that are guaranteed fresh, and also that for any sequence, ~e = e e e ; : : : , the labeling function lab (~e) = `1 : e `2 : e `3 : e ; : : : , takes a sequence of expression and creates a sequence of elds. The idea to retrieve positional notation is to label all elds of tuples, for instance out e e becomes out `1 : e `2 : e . Similarly for inputs and then the subsumption rule will line up elds in the right order. Actually this is not quite right as our matching rules still allow shorter object to match longer ones. So we add a function ar = (`1 ; 1); (`2 ; 2); : : : . The correct translation is thus out `1 : e `2 : e `lgt : ar (2) . With this translation it is fairly obvious that positional notation may be mixed with locked elds giving a rich choice of programming styles. 0
0
00
00
h
h
0
i
i
f
h
0
0
g
i
3.6 Summary
We have presented secure spaces, a coordination model that extends generative communication with ne grained access control primitives. We have shown that these primitives are adequate for implementing more powerful security mechanisms and that we have lost none of the expressiveness of Linda. We now turn to the question of practicality and argue that our extensions can be eciently implemented and used in a main stream programming language such as Java.
4 An Implementation of SecOS in Java This section describes an implementation of SecOS in Java 2. We show that the model can be eciently and securely implemented. Though the model was conceived for the JavaSeal mobile agent system [5], the implementation is independent of that platform. We also discuss several problems that have to be addressed when implementing a Linda-like model securely in an objectoriented programming language. This section is organized as follows. We overview the SecOS API and give an example of its use in paragraph 4.1. We discuss some of the security issues raised in implementing the model in 4.2. We nally look at the implementation of SecOS in 4.3, comparing it with JavaSpaces and we give some performance measures.
4.1 The SecOS API
Secure spaces are implemented by a small number of classes. Figure 3 shows their public interfaces. The class SSpace implements a shared space. Objects stored in a secure space belong to the SObj class. They contain SFields which are pairs of keys and values. Keys are represented by SKey objects. Values need only implement the serialization interface. The wildcard is represented by null. SObj are constructed with an array of elds. If this array is empty then the SObj represents the empty object, in the core language it is . A SObj can be extended with additional elds by invoking the extend method which returns a copy of the original object. Values can be selected from an object by invoking selectwith a key argument. If the object contains a eld with that key, the corresponding value will be extracted otherwise null is returned. Finally, an object can be locked with the lock method. This method take a key argument and returns a new object with olocked under that key. The matching rules enforces the olock semantics. Only a request with a matching key will retrieve the olocked object. Since we are concerned with shipping SObjs with agents to remote sites where they can be used in another space, it is convenient to ensure that keys are globally unique. Each Key object has a unique identi er, a string composed of the site IP address on which it was created, and a locally hi
15
public final class SSpace f public SSpace() public SObj in(SObj); public SObj in(SField[]); public void out(SObj); public void out(SField[]);
g
public final class SObjf public SObj(SField[]); public SObj extend(SField); public Serializable select(SKey ); public SObj lock(SKey);
g
public final class SFieldf public SField(SKey, Serializable);
g
public final class SKey implements Serializablef public SKey();
g
Figure 3: Core SecOS classes generated value which is unique for that site. This value is used when matching keys, two keys are only equal if they have the same identi er. Values are stored in the space in their serialized form, for reasons that we outline in section 4.2. We now show the SecOS version of the example of Section 3.2. We have three processes: A, B, and session key server (N). A and B want to communicate securely. They share keys ka and kb with N. A and N knows the public key KB for B's alock. After obtaining the sessionKey, B sends the message "On a clear disk, you can seek forever.". // Name server N SKey ka = new SKey(); // Key shared with A SKey kb = new SKey(); // Key shared with B // Await request from A SObj mes = new SObj().lock(ka); SObj res = space.in(mes); // Send message to B SObj snd = new SObj(fnew Field(KB, res)g).lock(kb); // Process A SKey sessionKey = new SKey(); SObj mes = new SObj().lock(ka); mes.extend(KB,sessionKey); space.out(mes); SObj tmp = new SObj().lock(sessionKey); SObj res = space.in(tmp);
16
// Process B SObj mes = new SObj().lock(kb); SObj res = space.in(mes); Key sessionKey = (Key) res.select(privKB).select(privKB); // Send a message to A SObj msg = new SObj().extend("On a clear disk, you can seek forever."); msg.lock(sessionKey); space.out(msg);
4.2 Some Security Issues
A SecOS implementation must conform to the semantics of the core language. There are two key security properties. First, access to an object placed in a space is only possible if the client program presents the correct key. Second, the space should be tamperproof. Furthermore, for our application we insist that secure space should be the only communication mechanism between agents. This implies that implementation must not break con nement. In a Java implementation there are two special language features that must be treated with care: sharing and subtyping. Sharing occurs whenever an object is referenced by more than one client [15] A problem in itself, as it makes programs hard to maintain and debug, sharing can create bridges between protection domains (agents in our case). This can result in integrity attacks against the kernel if an agent can get a pointer into a some low-level data structure and also open unregulated communication channels. The second point, subtyping, can be used by a malicious agent to inject code into the kernel or another agent [5]. Examples include replacing a SecOS core class with a Trojan horse. Or overriding the match method to mount a denial of service attack by an in nite loop. To prevent sharing, the out operation places a copy of each object in the space, and the code of SecOS ensures that only the space possesses a reference for that copy. Injection attacks through the matching process are prevented by using a system de ned match function on objects that is nal. SecOS in fact places a serialized version of an object's copy, as a MarshalledObject object. The equals method of that class is used as the system's match function to compare values. To prevent injection attacks on the classes that make up the SecOS, its core classes are declared as nal and are Java package protected from the rest of the system.
4.3 Some Implementation Issues
The two basic space operations in and out are the bottleneck for performance of secure spaces. We will describe our implementation and give performance data. Matching in SecOS is more dicult than in a traditional Linda tuple space because of subsumption. In Linda implementation the arity of a tuple (and in JavaSpaces its class) are useful guides for constructing indices. This does not apply our case. A shorter object can match a longer one, regardless of the order of elds. The rst issue is thus to construct a fast inequality test. The search procedure typically performs a large number of unsuccessful matches before. For this purpose we construct a ngerprint for each object. Fingerprints are bit strings with the property that if o o then fp (o) is a subset of fp (o ). We represent a ngerprint as an integer and for every key and every value in the object we set one bit at that element's value module the 32. Keys have unique integer values, while eld values are stored in serialized form so we use their hash code.
for (int j = 0; j < size; j++)
f
17
0
0
if ( k != f = f if ( v != f = f
null) | (1