Walnut/Secure Distributed Computing/Capability Patterns

From Erights

(Difference between revisions)
Jump to: navigation, search
m (Pet Names and Forgery among partially trusted participants)
m (Basic Claim Check Default Behavior)
Line 344: Line 344:
Suppose the owner of the car loses the claim check. He can still prove he owns the car by presenting another key. In software, this situation would be comparable to the situation in which the owner hands the attendant a direct reference rather than handing the attendant merely the claim check. The car owner has violated his own security, but it is hard to visualize situations in which this is not a reasonable argument to pass to express his desire. We can cover this case with a small modification to the claimMgr's reclaim method:
Suppose the owner of the car loses the claim check. He can still prove he owns the car by presenting another key. In software, this situation would be comparable to the situation in which the owner hands the attendant a direct reference rather than handing the attendant merely the claim check. The car owner has violated his own security, but it is hard to visualize situations in which this is not a reasonable argument to pass to express his desire. We can cover this case with a small modification to the claimMgr's reclaim method:
-
<pre>
+
<pre>to reclaim(claim) :any {
-
 
+
    try {
-
        to reclaim(claim) :any {
+
        def car := carKeys[claim]
-
            <font color="#0000FF">''try {''</font>
+
        carKeys.removeKey(claim)
-
                def car := carKeys[claim]
+
        return car
-
                carKeys.removeKey(claim)
+
    } catch prob {
-
                return car
+
        if (carKeys.contains(claim)) {
-
            <font color="#0000FF"> ''} catch prob {
+
            return claim
-
                if (carKeys.contains(claim)) {
+
        } else {throw(prob)}
-
                    return claim
+
    }
-
                } else {throw(prob)}
+
}</pre>
-
            }''</font>
+
-
        }
+
-
 
+
-
</pre>
+
What should the claimMgr return if the carOwner hands in a car reference for reclaim, if the car is not actually in the claimMgr's parking lot? The answer depends on the application, but such a situation violates the expectations of all the participants sufficiently that throwing an exception seems the safest choice.
What should the claimMgr return if the carOwner hands in a car reference for reclaim, if the car is not actually in the claimMgr's parking lot? The answer depends on the application, but such a situation violates the expectations of all the participants sufficiently that throwing an exception seems the safest choice.

Revision as of 10:34, 4 December 2006


Contents

Capability Patterns

Facets

As discussed in the minChat security review, facets are objects that act as intermediaries between powerful objects and users that do not need (and should not be granted) its full power. We saw a facet in use in the audited version of the eChat program, where the chatFacet. was interposed between the chatController and the remote chat participant. Here is a little general-purpose facet maker:


 # E sample
 /**
  * <param> target is the underlying powerful object
  * <param> allowedMethods is a map. The key is a method name, the value
  *       is the set of allowed numbers of arguments. So ["receive" =>[0, 2].asSet()]
  *       would allow the receive method, with either 0 or 2 arguments, to be forwarded.
 **/
 def makeFacet(target, allowedMethods) {
     def filteringFacet {
         match [verb, args] {
             if (allowedMethods.maps(verb) && 
                   allowedMethods[verb].contains(args.size())) {
                 return E.call(target, verb, args)
             }
         }
     }
     return filteringFacet
 }
 def chatController
 def chatFacet := makeFacet(chatController, ["receive"=>[1].asSet(),
                                             "receiveFriend"=>[1].asSet()])
 

Facets of this type can be retrofitted onto an existing system. We did this with very little effort for minChat with the chatFacet, but the technique works for far more complicated problems as well. The capability-secure windowing toolkits that E has placed on top of Swing and SWT uses facetization as the main tool.

Facets can be made much more sophisticated in their restrictions on access to their underlying object. You can make a facet that logs requests and sends email when certain methods are called. In an SEC-regulated stock exchange facet, you might wish to grant the capability to make a trade only from 9AM to 3PM excluding weekends and holidays.

One interesting example is the use-once facet, which allows the holder of the facet to use the facet only one time. For example, this version of a chatReceiver only allows a single message to be sent:


 # E sample
 def onceOnlyReceiver(baseChatController) {
     var chatController := baseChatController
     def onceOnlyReceiver {
         to receive(text) {
             chatController.receive(text)
             chatController := null
         }
     }
 }

This version will throw an exception back to the sender of a second message.

It can be tempting in a facet to suppress a couple of powerful methods in the underlying powerful object and delegate the rest.


 # E sample
 def powerfulObject {
    to doPowerfulOperation() {
         #do powerful operation
    }
    to doWeak1() {}
    to doWeak2() {}
    to doWeak3() {}
    #....
    to doWeak99() {}
 }
 def badFacet extends powerfulObject {
     to doPowerfulOperation() {
         #do nothing, no forwarding for the powerful operation, but forward everything else
     }
 } 

Avoid this. For a facet to remain secure during maintenance, it should never just delegate by default. If a new method is added to a powerful object (in this example, suppose powerfulObject is updated with a new method, doPowerful2()), it should not be exposed through the facet by default: rather, the facet must by default not expose it.

This risk can be exhausting to avoid, but it is always dangerous to accept. The first version of the capability windowing toolkit on Swing suppressed the handful of dangerous methods in the Java version 1.3 of Swing rather than explicitly allowing the safe methods, of which there were thousands. Within 30 days, the entire system was broken: the Java 1.4 Beta included hundreds of new, security-breaking methods. The only saving grace was that we had always known that the first version, thrown together in haste on weekends, was just a proof-of-principle and would have to be replaced. We just hadn't appreciated how soon replacement would be required.

Revocable Capabilities

If you wish to give someone restricted access to an object with facets, it is quite likely you will want to revoke access at some point as well.

The simplest way of making a capability revocable is to use a transparent forwarder that includes a revocation method:


 # E sample
 def revocableCapabilityMaker(baseCapableObject)  {
     var capableObject := baseCapableObject
     def forwarder {
         to revoke() {capableObject := null}
         match [verb, args] {E.call(capableObject, verb, args)}
     }
     return forwarder
 }

Note that, even though the forwarder is nominally delegating all method invocations (except revoke()) to the capableObject, we cannot use the extends keyword to capture the behavior. "Extends" creates an immutable reference, so the fact that the capableObject is a var wouldn't allow you to revoke the delegating behavior. Instead we use the match[verb, args] pattern.

Capability revocation, like facet forwarding, can be based on complex sets of conditions: revoke after a certain number of uses, revoke after a certain number of days. This example uses a simple manual revocation. Indeed, this version is too simple to work reliably during system maintenance and upgrade, and a more sophisticated pattern is generally recommended:


 # E sample
 def makeCapabilityRevokerPair(baseCapableObject) {
     var capableObject := baseCapableObject
     def revoker {
         to revoke() {capableObject := null}
     }
     def forwarder {match[verb, args] {E.call(capableObject, verb, args)}}
     return [forwarder, revoker]
 }
 def powerfulObject {
     #...big powers 
 }
 def [power, revoker] := makeCapabilityRevokerPair(powerfulObject)

In this pattern, the authority of the object and the authority to revoke are separated: you can hand the power to revoke to an object you would not trust with the authority itself. Also, the separate revoker can itself be made revocable.

Sealers and Unsealers

A sealer/unsealer pair makes it possible to use untrusted intermediaries to pass objects safely. Use the sealer to make a sealed box that can only be opened by the matching unsealer. E has built-in support for sealer/unsealer pairs:


 ? def makeBrandPair := <elib:sealing.makeBrand>
 # value: <makeBrand>
 
 ? def [sealer, unsealer] := makeBrandPair("BrandNickName")
 # value: [<BrandNickName sealer>, <BrandNickName unsealer>]
 
 ? def sealedBox := sealer.seal("secret data")
 # value: <sealed by BrandNickName>
 
 ? unsealer.unseal(sealedBox)
 # value: "secret data"
 

If you hold the unsealer private, and give the sealer away publicly, everyone can send messages that only you can read. If you hold the sealer private, but give the unsealer away publicly, then you can send messages that recipients know you created, i.e., you can use it as a signature. If you are thinking that this is much like a public/private key pair from public key cryptography, you are correct, though in E no actual encryption is required if the sender, recipient, brand maker, and sent object all reside in the same vat.

While the Brand is built-in, it is possible to construct a sealer/unsealer pair maker in E without special privileges. The "shared variable" technique for making the maker is interesting, and because the same pattern appears in other places (such as the Notary/Inspector, coming up next), we demonstrate it here:


 # E sample
 def makeBrandPair(nickname) {
     def noObject{}
     var shared := noObject
     def makeSealedBox(obj) {
         def box {
             to shareContent() {shared := obj}
         }
         return box
     }
     def sealer {
         to seal(obj) {return makeSealedBox(obj)}
     }
     def unsealer {
         to unseal(box) {
             shared := noObject
             box.shareContent()
             if (shared == noObject) {throw("invalid box")}
             def contents := shared
             shared := noObject
             return contents
         }
     }
     return [sealer, unsealer]
 }
 

The variable "shared" normally contains the value "noObject", which is private to a particular sealer/unsealer pair and so could never the the actual value that someone wanted to pass in a sealed box. The unsealer tells the box to shareContent, which puts the content into the shared variable, from which the unsealer then extracts the value for the invoker of the unseal method. In a conventional language that used threads for concurrency control, this pattern would be a disaster: different unseal requests could rumble through, overwriting each others' shared content. But by exploiting the atomicity of operational sequences enabled by promise pipelining, this peculiar pattern becomes a clean solution for this, and several other security-related operations.

Vouching with Notary/Inspector

Suppose Bob is the salesman for Widget Inc. He persuades Alice to buy a widget. Bob hands Alice a Widget Order Form with a money-receiving capability. It is important to Bob that Alice use the form he gives her, because this particular form (which Bob got from Widget Inc.) remembers that Bob is the salesman who should get the commission. It is important to Alice that she know for sure that, even though she got the order-form from Bob, this is really a Widget Inc. order form, and not something Bob whipped up that will transfer her money directly to his own account. In this case, Alice wants to have Widget Inc. vouch for the order-form she received from Bob. She does this using an Inspector that she gets directly from Widget Inc. The Inspector is the public part of a notary/inspector pair of objects that provide verification of the originator of an object. To be vouchable, the orderForm must implement the startVouch method as shown here:


 # E sample
 ####### Widget Inc. software #####
 
 #vouching system 
 #returns a private notary that offers a public inspector
 #throws problem if the object being vouched is not vouchable
 def makeNotary()  {
     def nonObject {}
     def unvouchedException(obj) {throw(`Object not vouchable: $obj`)}
     var vouchableObject := nonObject
     def inspector {
         to vouch(obj) {
             vouchableObject := nonObject
             try {
                 obj.startVouch()
                 if (vouchableObject == nonObject) {
                     return unvouchedException(obj)
                 } else {
                     def vouchedObject := vouchableObject
                     vouchableObject := nonObject
                     return vouchedObject
                 }
             } catch err {unvouchedException(obj)}
         }
     }
     def notary {
         to startVouch(obj) { vouchableObject := obj}
         to getInspector()  {return inspector}
     }
     return notary
 }
 
 #create Widget Inc's notary
 def widgetNotary := makeNotary()
 
 #Order form maker
 def makeOrderForm(salesPerson) {
     def orderForm {
         # .... methods for implementing orderForm
         to startVouch() {widgetNotary.startVouch(orderForm)}
     }
     return orderForm
 }
 
 #publicly available inspector object 
 #(accessible through a uri posted on Widget Inc's web site)
 def WidgetInspectionService {
     to getInspector() {return widgetNotary.getInspector()}
 }
 
 ##### bob software #####
 
 #scaffold for sample
 def getOrderFormFromBob()  {return makeOrderForm("scaffold")}
 
 ########## Alice's software to vouch for the order form she received from Bob #####
 
 def untrustedOrderForm := getOrderFormFromBob()
 def inspector := WidgetInspectionService.getInspector()
 def trustedOrderForm := inspector.vouch(untrustedOrderForm)

Proof of Purchase

"Proof of Purchase" is the simplest of a series of capability patterns in which the goal is not to transfer an authority, but rather to show someone that you have the authority so that they can comfortably proceed to use the authority on your behalf. In Proof of Purchase, the requester of a service is demonstrating to the server that the client already has capability that the server is being asked to use. Unlike the more sophisticated upcoming Claim Check patterns, the proof of purchase client is not concerned about giving the server an excess authority.

An interesting example of Proof of Purchase is Component.transferFocus(fromComponentsList, toComponent) found in the tamed Swing package. In standard Swing, the holder of any panel reference can steal the focus (with component.requestFocus()). Hence any keystrokes, including passwords, can be swiped from wherever the focus happens to be by sneaking in a focus change. This is a clear violation of POLA. You should be able to transfer focus from one panel to another panel only if you have references to both panels. If you have a reference to the panel that currently has the focus, then you can never steal the focus from anyone but yourself.

A natural-seeming replacement for component.requestFocus() would be requestFocus(currentFocusHolder). It was decided during the taming of Swing, however, that this would be breach-prone. If Alice transferred to Bob, not a panel itself, but rather a simple transparent forwarder on that panel, Alice could collect authorities to other panels whenever Bob changed focus.

Since we had a convenient trusted third party available that already had authority over all the panels anyway (the javax.swing.Component class object), we used the "proof of purchase" pattern instead. The globally available Component.transferFocus method accepts two arguments:

  • a list of panels that the client believes to contain, somewhere in their subpanel hierarchies, the focus
  • the target panel that should receive the focus

In the common case where a whole window lies inside a single trust realm, the client can simply present a reference to the whole window and be confident that the focus will be transferred.

The interesting thing about Component.transferFocus is that the recipient does not actually need the client's authority to reach the component that already has the focus. The Component class already has that authority. The client must send the authority merely to prove he has it, too.

Basic Claim Check

A common activity that brings security concerns into sharp focus is the delicate dance we perform when we use a car valet service. In this situation, we are handing the authority over our most precious and costly possession to a teenager with no more sense of responsibility than a stray cat. The whole valet system is a toothsome exercise in POLA.

In this example, we focus on the sequence of events and trust relationships involved in reclaiming our car at the end of the evening. The participants include the car owner, the valet service itself, and the random new attendant who is now on duty.

We have already chosen, for better or for worse, to trust the valet service with a key to the car. As the random new attendant comes running up to us, we are reluctant to hand over yet another key to the vehicle. After all, this new attendant might actually be an imposter, eager to grab that new Ferrari and cruise out over the desert. And besides, we already handed the valet service a key to the car. They should be able to use the key they already have, right?

Meanwhile, the attendant also has a trust problem. It would be a career catastrophe to serve up the Ferrari from his parking lot if I actually own the dented 23-year-old Chevy Nova sitting next to it.

In the physical world, we use a claim check to solve the problem. We present to the attendant, not a second set of car keys, but rather, a proof that we have the authority that we are asking the attendant to use on our behalf.


 # E sample
 def claimMgr := {
     def carKeys := [].asMap().diverge()
     def claimMgr {
         to makeClaim(car) :any {
             # the claim object has no interesting 
             # properties except it has
             # a unique unforgeable identity
             def claim {}
             carKeys[claim] := car
             return claim
         }
         to reclaim(claim) :any {
             def car := carKeys[claim]
             carKeys.removeKey(claim)
             return car
         }
     }
 }
 
 def attendant {
     #return a claim check when car is parked
     to parkCar(car) :any {
         # ...code to park the car...
         return claimMgr.makeClaim(car)
     }
     to retrieveCar(claim) :void {
         def car := claimMgr.reclaim(claim)
         # ...code to retrieve car...
         # no need to return the car reference, the owner of the claim
         # presumably already has such a reference
     }
 }
 
 def car {}
 def carOwner := {
     var claim := null
     def carOwner {
         to letAttendantParkCar() :void {
              claim := attendant.parkCar(car)
              println(claim)
         }
         to getCarFromAttendant() :void {
             println(attendant.retrieveCar(claim))
         }
     }
 }
 
 carOwner.letAttendantParkCar()
 carOwner.getCarFromAttendant()
 

Basic Claim Check Default Behavior

Suppose the owner of the car loses the claim check. He can still prove he owns the car by presenting another key. In software, this situation would be comparable to the situation in which the owner hands the attendant a direct reference rather than handing the attendant merely the claim check. The car owner has violated his own security, but it is hard to visualize situations in which this is not a reasonable argument to pass to express his desire. We can cover this case with a small modification to the claimMgr's reclaim method:

to reclaim(claim) :any {
    try {
        def car := carKeys[claim]
        carKeys.removeKey(claim)
        return car
    } catch prob {
        if (carKeys.contains(claim)) {
            return claim
        } else {throw(prob)}
    }
}

What should the claimMgr return if the carOwner hands in a car reference for reclaim, if the car is not actually in the claimMgr's parking lot? The answer depends on the application, but such a situation violates the expectations of all the participants sufficiently that throwing an exception seems the safest choice.

NonTransferable Claim Check

We are far from done with claim checks. A careful car owner will not actually hand his claim check to the parking lot attendant. Rather, he will merely show the claim check to the attendant. After all, if you hand the claim check over to an imposter, the imposter can turn around and hand the claim check to a real attendant, pretending that he is the owner of the car.

The problem is somewhat different in cyberspace. The good news is, in cyberspace the attendant doesn't return a reference to the car just because you hand him the claim check. Instead, he merely performs the action you ask of him with the car. This set of actions is limited by the attendant's willing behavior. So the claim check is not as powerful a capability as the car itself. But it can still be a powerful capability. And the bad news is, in cyberspace, you can't just "show" it to the attendant. You have to give it to him.

Here we demonstrate a "nontransferable" claim check. This claim check can be handed out at random to a thousand car thieves, and it does them no good. The trick is, before handing over the claim check, the owner inserts into the claim check a reference to the individual he is treating as an attendant. The ClaimMgr compares the person to whom the owner handed the claim, with the attendant who eventually hands the claim to the claimMgr . If these two people are the same, then the owner handed the claim directly to the attendant. Otherwise, there was an intermediary party, and the request should not be honored.


 # E sample
 def makeBrandPair := <elib:sealing.makeBrand>
 def valetService := {
     def [claimSealer, claimUnsealer] := makeBrandPair("claim")
     def claimMgr {
         to makeClaim(car) :any {
             def transferableClaim {
                 to makeNontransferableClaim(intendedRecipient) :any {
                     return claimSealer.seal([car, intendedRecipient])
                 }
             }
             return transferableClaim
         }
         to reclaim(claim, actualRecipient) :any {
             def [car, intendedRecipient] := claimUnsealer.unseal(claim)
             if (actualRecipient == intendedRecipient) {
                 return car
             } else {throw("claim not transferable, invalid attendant")}
         }
     }
     def valetService {
         to authorizeAttendant(attendant) :void {
             attendant.setClaimMgr(claimMgr)
         }
     }
 }
 
 def makeAttendant() :any {
     def claimMgr
     def attendant {
         to setClaimMgr(mgr) :void {bind claimMgr := mgr}
         to parkCar(car) :any {
             # ...code to park the car...
             return claimMgr.makeClaim(car)
         }
         to retrieveCar(claim) :void {
             def car := claimMgr.reclaim(claim, attendant)
             # ...code to retrieve car...
             # no need to return the car reference, the owner of the claim
             # presumably already has such a reference
         }
     }
     return attendant
 }
 
 def legitAttendant := makeAttendant()
 valetService.authorizeAttendant(legitAttendant)
 
 def carThief {
     to parkCar(car) :any {
         return println ("Ha! stole the car")
     }
     to retrieveCar(claim) :void {
         try {
             legitAttendant.retrieveCar(claim)
             println("Ha! didn't get car, but got control")
         } catch prob {println(`rats! foiled again: $prob`)}
     }
 }
 
 def carOwner := {
     def car{}
     var claim := null
     def carOwner {
         to letValetPark(attendant) :void {
              claim := attendant.parkCar(car)
              println(claim)
         }
         to letValetRetrieve(attendant) :void {
             def noTransferClaim := claim.makeNontransferableClaim(attendant)
             println(attendant.retrieveCar(noTransferClaim))
         }
     }
 }
 
 carOwner.letValetPark(legitAttendant)
 carOwner.letValetRetrieve(carThief)
 carOwner.letValetRetrieve(legitAttendant)
 

Note an important implication in this pattern: the ClaimMgr winds up in a position to accumulate references to all the entities a claim check holder treats as an attendant. In this example, the ClaimMgr gets a reference to the carThief as a result of the carOwner's casual handing out of claim checks. It seems unlikely that the ClaimMgr can harm the carOwner's interests with this reference, but in other circumstances the reference may not be so harmless. To reduce our risk exposure to the alleged attendants to whom we hand the claim check, we have increased our risk exposure to the ClaimMgr.

Note also the limitations on the nontransferability of this pattern. The carOwner can still transfer authority, either by handing someone a reference to the car, or by handing out the transferableClaim from which nontransferableClaims can be made. The nontransferability is voluntarily chosen by the carOwner. You can't prevent people from delegating their authority, and even this pattern doesn't change that fact.

Oblivious Claim Check: Loan Officer Protocol

Voluntary Oblivious Compliance, or VOC, was pioneered by the Client Utility System. VOC is a field only recently recognized by the capability-security community as an important area of exploration; the claim check patterns here are early fruit of that research.

VOC is irrelevant in the classical cypherpunk view of the world, where every person is a rugged individualist making all his own authority-granting decisions with little regard for other people's issues. In a world filled with corporations, governments, and other policy-intensive organizations, however, it is an area of real value even though it is not really a security matter. Why is it not security? We consider it beyond the scope of "security" for a simple but compelling reason: it is not enforceable. Unenforceable security in cyberspace has been proven, over and over again in the course of the last decade, to be a joke played on all the participants. VOC states in its very name the limits of its applicability: it only works with volunteers. Don't be fooled into thinking this is security.

Let us consider a somewhat different problem. Suppose that different attendants are trusted to handle different cars by the valet service. One valet has a motorcycle license and parks all the Harleys. Another has a multi-engine pilot's license and parks all the Boeing 747s. Of course, the one with the motorcycle license is a teenager who has always wanted to try his hand at parking a 747, and knows his lack of experience is not a problem. In this situation, each attendant has a different set of authorities at his command; just because you hand your claim check to a legit attendant doesn't mean the valet service thinks it would be a good idea to let that attendant drive your vehicle. A more generalized way of stating the problem is, in this case the authorities of the individual receivers of the claim checks vary, and management of the individual receiver's authorities is beyond the scope of what the ClaimManager should be trying to figure out. After all, the individuals know all their own authorities; it would be poor design (and unmaintainable at scale) for the ClaimManager to try to duplicate this information.

This situation, while not a part of the real-world valet service problem, has a significant area of application in cyberspace. This area is Voluntary Oblivious Compliance, or VOC. Let us consider a more sensible example. Alice works for HPM, and Bob works for Intil. HPM and Intil are often competitors, but Alice and Bob are working on a joint project from which both companies expect to profit handsomely. The Official Policy Makers of HPM have identified a number of documents which can be shared with Intil, and indeed have forwarded to Intil references to the allowed docs. Alice wants to refer Bob to a particular HPM document, but only if sharing the document is allowed under the HPM policy. In this case, the VOCclaimCheck that Alice sends to Bob demands that Bob demonstrate to HPM's ClaimMgr that he already has authority on the document before fulfilling the claim. To prove his authority, Bob sends to the ClaimMgr all the HPM doc authorities he has (i.e., the list of docs HPM handed to Intil). Only if both Alice and Bob already have authority on the object does Bob get the document. This is sometimes called the Loan Officer Protocol, in reference to the old adage that a loan officer will not give you the loan unless you first prove that you don't need it.

Since bob can't get the reference unless he proves he doesn't need it, we can now see why this is a pattern of Voluntary Oblivious Compliance. It is voluntary, because Alice could just send Bob the document and circumvent the system. And it is oblivious, because Alice doesn't need to know whether a document can be shared with Bob before sending it.

Going back to the parking lot attendant who wants to park the 747, he has to demonstrate to the ClaimManager that he has the keys to the 747 before the Claim Manager will let him go for it. The owner of the 747 is much relieved.

Humorous as the 747 example might be, we will now switch to the scenario with Alice, Bob, HPM, and Intil for our sample code. The basic strategy is that the ClaimMgr not only examines the claim check from Alice, it also examines the list of authorities that Bob hands over to see if any of Bob's authorities match the one inside Alice's claim.


 # E sample
 def makeBrandPair := <elib:sealing.makeBrand>
 def [claimSealer, claimUnsealer] := makeBrandPair("claim")
 def HPMDocClaimMgr {
     to makeClaim(doc) :any {
         return claimSealer.seal(doc)
     }
     to matchClaim(claim, listOfCandidates) :any {
         def doc := claimUnsealer.unseal(claim)
         for each in listOfCandidates {
             if (doc == each) {return each}
         }
         throw("No match from claimCheck to candidate auths")
     }
 }
 
 def intilSharedDocs := [def doc1{}, def doc2{}, def doc3{}]
 
 def bob {
     to lookAtHPMDoc(claim) :void {
         try {
             def doc := HPMDocClaimMgr.matchClaim(claim, intilSharedDocs)
             println(`reading doc: $doc`)
         } catch prob {
             println(`can't read: $claim`)
         }
     }
 }
 
 def alice {
     to sendDoc(doc, recipient) :void {
         def claim := HPMDocClaimMgr.makeClaim(doc)
         recipient.lookAtHPMDoc(claim)
     }
 }
 alice.sendDoc(doc2, bob)
 alice.sendDoc(def doc4{}, bob)
 

The oblivious claim check pattern can require scattering even more authority to the winds than the nontransferable claim check. The recipient of the claim check (bob) needs to have all the authorities that alice might give to him, rather than merely the ones she actually does give to him. And the trusted third party who matches the claim check against the list of possibilities (the HPMDocClaimMgr) gets to accumulate authority to everything that bob thinks alice might be trying to send him. In the example scenario, this is just fine. But some care is required.

Oblivious Claim Checks as Guards

An elegant style of using VOC claim checks is to set up the pattern as a pair of guards. Alice would use the ClaimCheck guard to send a reference to Bob, and Bob would use the CheckedClaim guard, with the list of candidate references, to receive the authority. We will show the implementation of the BuildGuards ClaimCheck and CheckedClaim guards in Advanced Topics when we talk about writing your own guards, but their usage would appear as follows:


 # E syntax
 def bob {
     to lookAtHPMDoc(doc ''<font color="#0000FF">:CheckedClaim[intilSharedDocs]</font>'') :void {
         if (!E.isBroken(doc)) {
             println(`reading doc: $doc`)
         } else {println(`can't read: $claim`)}
     }
 }
 
 def alice {
     to sendDoc(doc, recipient) :void {
         recipient.lookAtHPMDoc(doc <font color="#0000FF">'':ClaimCheck''</font>)
     }
 }

The ClaimCheck guard coerces the doc into a sealed representation of itself. The CheckedClaim guard unseals the claim and matches it against the authorities in the intilSharedDocs list. As a guard, it is better style to return a broken reference than to throw an exception if something goes wrong. In the parameter guard, if an exception were thrown, the recipient would have no chance to do anything with the problem, the exception would be thrown directly back to the sender before the recipient was even aware there was an issue.

Oblivious NonTransferable Claim Check

It is possible to make nontransferable oblivious claim checks as well. We leave the code as an exercise for the reader.

Powerbox Capability Manager

The powerbox pattern collects diverse elements of authority management into a single object. That object, the powerbox, then becomes the arbiter of authority transfers across a complex trust boundary. One of the powerbox's most distinctive features is that it may be used for dynamic negotiations for authority during operation. The less trusted subsystem may, during execution, request new authorities. The powerbox owner may, in response to the request, depending on other context that it alone may have, decide to confer that authority, deny the authority, or even grant the request authority after revoking other authorities previously granted.

The powerbox is particularly useful in situations where the object in the less trusted realm does not always get the same authorities, and when those authorities may change during operation. If the authority grant is static, a simple emaker-style authorization step suffices, and a powerbox is not necessary. If the situation is more complex, however, collecting all the authority management into a single place can make it much easier to review and maintain the extremely security-sensitive authority management code.

Key aspects of the powerbox pattern include:

  • A powerbox uses strict guards on all arguments received from the less trusted realm. In the absence of guards, even an integer argument received from untrusted code can play tricks: the first time the integer-like object (that is not really an integer) is asked to "add", it returns the value "123"; but the second time, it returns the value "456", with unpredictable (and therefore insecure) results. An ":int" guard on the parameter will prevent such a fake integer from crossing into your realm.
  • A powerbox enables revocation of all the authorities that have been granted. When you are done using a less trusted subsystem, the authorities granted to it must be explicitly revoked. This is true even if the subsystem is executing in your own vat and you nominally have the power to disconnect all references to the subsystem and leave the subsystem for the garbage collector. Even after being severed in this fashion, the subsystem will still exist for an unbound amount of time until the garbage collector reaches it. If the authorities it has received have not been revoked, it can surprise you with its continued operations, and continued use of authority. Not all kinds of objects in the Java API can be made directly revokable at this time, because an E revokable forwarder cannot be used in all the places where an actual authority-carrying Java object is required. For example, the untrusted object may want to create a new image from an url. The natural way of doing this would be to call the Java Icon class constructor with (url) as the argument. But if an E forwarder were handed to this class constructor, the constructor would throw a type exception.Because of this anomally, different solutions are required in different situation. For the icon maker, it might be acceptable to require that the untrusted object read the bits of the image from the url (through a revokable forwarder) and then convert the bits into an icon.
  • A new powerbox is created for each subsystem that needs authorities from within your trust realm. If a powerbox is shared across initiations of multiple subsystems, the powerbox may become the channel by which subsystems can illicitly communicate, or the channel by which an obsolete untrusted subsystem can interfere with a new one. When the old untrusted subsystem is discarded, its powers must all be revoked, which necessarily implies that a new subsystem will need a new powerbox.

In the following example, the less trusted object may be granted a Timer, a FileMaker that makes new files, and a url. The object may request a different url in the course of operations, in which case the powerbox will ask the user for authorization on the objects's behalf; the old url is revoked, and the new one substituted, so that the object never has authority for more than one url at a time. The object that operates the powerbox may revoke all authorities at any time, or it may choose to revoke the Timer alone. Finally, the operator of this powerbox may, for reasons external to the powerbox's knowledge, decide to grant an additional authority during operations, an authority whose nature is not known to the powerbox.

# E sample
# The authorized url may change during operations, so it is a var
def makePowerboxController(optTimer,
                           optMakeFile,
                           var optUrl,
                           optMakeUrl,
                           makeDialogVow) {
    
    # In the revokers map, the object being revoked is the key, the revoker
    # is the value. Note it is the actual object, not the forwarder to the
    # object, that is the key.
    var revokers := [].asMap()
    
    # when a revokable forwarder is made, the revoker is automatically
    # placed in the map of revokers
    def makeRevokableForwarder(object) :near {
        var innerObject := object
        def revoker {
            to revoke() {innerObject := null}
        }
        revokers with= (object, revoker)
        def forwarder {match [verb, args] {return E.call(innerObject, verb, args)}}
        return forwarder
    }
    
    # makeFileFacet is exposed to less trusted realm; guard the path it receives
    # This simple file facet only supplies 2 methods that return immutables
    # If the file handed out mutable objects, such as streams, these would have
    # to be wrapped with revokableForwarders as well. 
    def makeFileFacet(path :String) :near {
        def theFile := optMakeFile(path)
        def fileFacet {
            to getText() :String     {theFile.getText()}
            to setText(text :String) {theFile.setText(text)}
        }
        return makeRevokableForwarder(fileFacet)
    }
    
    # makeFile is actually handed to the less trusted object
    # It is either null or a revokable forwarder for makeFileFacet
    # In other words, makeFile is a revokable maker of revokable file facets
    def makeFile
    if (optMakeFile == null) {
        bind makeFile := null
    } else {
        bind makeFile := makeRevokableForwarder(makeFileFacet)
    }
    
    # Like the file facet, this url facet only supports methods that return
    # immutables. 
    def makeRevokableUrlFacet(optUrl) :near {
        if (optUrl == null) {
            return null
        } else {
            def urlFacet {
                to getBytes() :pbc {optUrl.getBytes()}
            }
            return makeRevokableForwarder(urlFacet)
        }
    }
    
    # Return a vow for a new url 
    # Use dialog with user to determine if new url should be granted
    # Vow resolves to null if anything goes wrong
    def makeRevokableUrlVow := {
        def makeUrlVow(requestedUrl :String, why :String) :vow {
            def urlDialogVow := 
              makeDialogVow("Url Request",
                            `<html>
Confined Object requesting url for this reason:<p>$why
</html>`,
                            requestedUrl,
                            ["Grant", "Refuse"])
            return when (urlDialogVow) -> {
                if (urlDialogVow.getButton() == "Grant") {
                    optUrl := optMakeUrl(urlDialogVow.getText())
                    makeRevokableUrlFacet(optUrl)
                } else {null}
            } catch prob {null}
        }
        return makeRevokableForwarder(makeUrlVow)
    }
    
    
    var caps := [].asMap()
    caps with= ("TIMER", makeRevokableForwarder(timer))
    caps with= ("FILEMAKER", makeFile)
    caps with= ("URL", makeRevokableUrlFacet(optUrl))
    
    def powerbox {
        
        # any of these capabilities may be null, i.e., all are optional
        # in a powerbox, strictly at the whim of the powerbox operator
        # who created the powerboxcontroller and the powerbox
        to optCap(key :String) :any {return caps.get(key, null)}
        
        # When the less trusted object requests a new url, the
        # old url is immediately revoked, and the promise for the
        # new url is substituted
        # If the powerbox has revokedAll, any attempt to requestUrl
        # will throw an exception back to the untrusted object
        # The "why" parameter is the less trusted object's justification
        # for needing the url
        to requestUrl(requestedUrl :String, why :String):vow {
            if (optUrl != null) {revokers[optUrl].revoke()}
            def revokableUrlVow := makeRevokableUrlVow(requestedUrl, why)
            caps with= ("URL", revokableUrlVow)
            return revokableUrlVow
        }
    }
    
    def powerboxController {
        to revokeAll() {
            for each in revokers {each.revoke()}
        }
        to revokeTimer() {revokers[timer].revoke()}
        # Confer an additional capability during execution
        to conferCap(key, cap) {
            caps with= (key, makeRevokableForwarder(cap))
        }
        to getPowerbox() {return powerbox}
    }
}
 
# now, show how to create a powerbox and hand it to an untrusted object
 
def makeUntrustedObject(powerbox) {
    def timer := powerbox.optCap("TIMER")
    def urlVow := powerbox.requestUrl("http://www.skyhunter.com")
    def untrustedObject {
        #...
    }
    return untrustedObject
}
 
def powerboxOperator() {
    def makeDialogVow {#...construct dialog vow
                      }
    # use real objects, not nulls, in typical operation, though nulls are valid arguments
    def controller := makePowerboxController(null,
                                             null,
                                             null,
                                             null,
                                             makeDialogVow)
    def powerbox := controller.getPowerbox()
    def untrusted := makeUntrustedObject(powerbox)
    # later, confer an additional authority
    def special
    controller.conferCap("SPECIAL", special)
    # when done with the untrusted object, revoke its powers
    controller.revokeAll()
}

Both the file facet and the url facet restrict the user to methods that return immutables. If these facets returned mutables, like streams, then those mutables would also have to be wrapped in revokable forwarders. In the general case, this requires the use of a membrane. write section on membranes, and link

Pet Names and Forgery among partially trusted participants

E's encrypted authenticated references ensure that random attackers get no traction trying to break into your distributed system. As mentioned in the earlier minChat audit, this means that all interesting break-ins will be made by "insiders", i.e., people who have been granted some capabilities inside your system. We have talked so far about low-level attacks that attempt to acquire or use inappropriately conveyed authority. At a higher conceptual level, people can try to present themselves as someone else and get your user to intentionally, but erroneously, grant other information or capabilities.

minChat dodged this problem because it is a 2-person chat system: the person at the other end of the line will have difficulty convincing your user he is someone else, because your user knows the one and only person given the URI (because he used PGP encryption as described earlier to ensure that only one person got it). But in a multi-person chat system, Bill could try to pretend to be Bob and lure your user into telling him Bob's secrets.

There is no single all-purpose solution to this risk. However, one critical part of any specific solution must be this: Do not allow the remote parties to impose their own names for themselves upon your user.

Pet_Names

The general solution is to use pet names, i.e., names your user chooses for people, regardless of how those people might name themselves. Of course, it is reasonable to allow the other people to propose names to your user (called nicknames), but the user must make the final decision. The user must also be able to change her mind at a later date, when her list of participants grows to include not only Bob Smith but also Bob Jones, and she must disambiguate the pet name "Bob" she had had for Smith.

The general layout of such a pet name setup might be as follows:


pet name sample
Personal tools
more tools