Responsibility Tracking (Horton)
One of the issues that is commonly raised with capabilities is being able to track the usage of a capability, and ensure it is correlated with an identity or “security principal” during operation.
The Horton protocol is a way to ensure capabilities can be used with attribution.
Horton is used for responsibility tracking. Using Horton, stubs and proxies are used in conjunction with dynamic sealing so that a proxy can always establish the provenance of a request as coming from a particular principal.
To the end user, most of this is transparent. Scala is statically typed, instances of proxies must be created at compile time, by the trait’s singleton object. After that, proxies are generated by establishing the from
and to
principals associated with the objects.
object Main {
import Principal.ProxyMaker
class A(b: B, c: C) {
def start(): Unit = {
b.foo(c)
}
}
trait B {
def foo(c: C): Unit
}
object B {
implicit val proxyMaker: ProxyMaker[B] = new ProxyMaker[B] {
override def makeProxy(stub: Stub[B])(implicit context: Context): Proxy[B] = {
import context._
def log(msg: String): Unit = {
reportln(s"I ask ${whoBlame.hint} to:\n> $msg")
}
new B {
override def foo(c: C): Unit = {
log("> foo/1")
stub.deliver("foo", c) // createDescription implicitly converts c
}
override def toString: String = "B Proxy"
}
}
}
}
trait C {
def hi(): Unit
}
object C {
implicit val proxyMaker: ProxyMaker[C] = new ProxyMaker[C] {
override def makeProxy(stub: Stub[C])(implicit context: Context): C = {
import context._
def log(msg: String): Unit = {
reportln(s"I ask ${whoBlame.hint} to:\n> $msg")
}
new C {
override def hi(): Unit = {
log("> hi/1")
stub.deliver("hi", this) // createDescription implicitly converts this
}
override def toString: String = "C Proxy"
}
}
}
}
def main(args: Array[String]): Unit = {
val b = new B {
def foo(c: C): Unit = {
c.hi()
}
override def toString: String = "B"
}
val c = new C {
def hi(): Unit = println("hi")
override def toString: String = "C"
}
val alice = new Principal("Alice", println)
val bob = new Principal("Bob", println)
val carol = new Principal("Carol", println)
// Gifts are sealed and can only be unsealed by the recipient.
val toAliceFromBob: Gift[B] = bob.encodeFor(b, alice.who)
val toAliceFromCarol: Gift[C] = carol.encodeFor(c, alice.who)
// When gift is unsealed, a proxy is returned that establishes provenance.
val p1: B = alice.decodeFrom(toAliceFromBob, bob.who)
val p2: C = alice.decodeFrom(toAliceFromCarol, carol.who)
val a = new A(p1, p2)
a.start()
}
}
Full source at GitHub
The output from running Main
is the following text:
Alice said:
> I ask Bob to:
> > foo/1
Carol said:
> Alice asks me to:
> > meet Bob
Bob said:
> Alice asks me to:
> > foo/1
Bob said:
> I ask Carol to:
> > hi/1
Carol said:
> Bob asks me to:
> > meet Carol
Carol said:
> Bob asks me to:
> > hi/1
hi
Because Horton uses stubs and proxies internally, the process of establishing a relationship and communicating messages involves several steps. These steps are internal to the operation, but are vital to ensure that the relationship between principals cannot be faked.
The slides for Horton are a useful guide for going through the steps.
The implementation is as follows:
package ocaps.examples
package object horton {
import ocaps.Brand._
trait Fill[T] extends (Stub[T] => Unit)
type FillBox[T] = Box[Fill[T]]
trait Provide[T] extends (FillBox[T] => Unit)
type Gift[T] = Box[Provide[T]]
trait Wrap[T] extends ((Stub[T], Who) => Gift[T])
trait Unwrap[T] extends ((Gift[T], Who) => Stub[T])
type Proxy[T] = T
}
Full source at GitHub
class Who(sealer: Sealer) {
def apply[T](provide: Provide[T]): Gift[T] = sealer(provide)
def apply[T](fill: Fill[T]): FillBox[T] = sealer(fill)
def hint: Hint = sealer.hint
override def toString: String = s"Who(sealer = $sealer)"
}
class Be(unsealer: Unsealer) {
def apply[T](gift: Gift[T]): Provide[T] = {
require(gift.hint == unsealer.hint, s"Gift hint ${gift.hint} does not match unsealer hint ${unsealer.hint}")
unsealer(gift).get
}
def apply[T](fillbox: FillBox[T]): Fill[T] = {
require(fillbox.hint == unsealer.hint, s"Fillbox hint ${fillbox.hint} does not match unsealer hint ${unsealer.hint}")
unsealer(fillbox).get
}
def hint: Hint = unsealer.hint
override def toString: String = s"Be(unsealer = $unsealer)"
}
trait Stub[T] {
def intro(whoBob: Who): Gift[T]
def deliver[ArgType](verb: String, desc: (Gift[ArgType], Who))
(implicit tTag: ru.TypeTag[T], cTag: ClassTag[T], proxyMaker: Principal.ProxyMaker[ArgType]): Any
}
trait ProxyAmps {
def put[T](key: T, value: (Stub[T], Who)): Unit
def apply[T](key: T): (Stub[T], Who)
}
class Principal(label: String, printer: String => Unit) {
private val proxyAmps = new ProxyAmpsImpl()
private val (whoMe, beMe) = {
val brand = Brand.create(label)
(new Who(brand.sealer), new Be(brand.unsealer))
}
val who: Who = whoMe
def encodeFor[T](targ: T, whoBlame: Who): Gift[T] = {
val stub = makeStub(whoBlame, targ)
wrap(stub, whoBlame)
}
def decodeFrom[T](gift: Gift[T], whoBlame: Who)(implicit proxyMaker: Principal.ProxyMaker[T]): T = {
val stub = unwrap(gift, whoBlame)
implicit val context: proxyMaker.Context = proxyMaker.Context(this, whoBlame, reportln)
val proxyB = proxyMaker.makeProxy(stub)
proxyAmps.put(proxyB, (stub, whoBlame))
proxyB
}
override def toString: String = s"Principal($label)"
private def makeStub[T](who: Who, t: T): StubImpl[T] = {
new StubImpl(who, t)
}
private def reportln(msg: String): Unit = {
printer(s"$label said:\n> $msg")
}
private def wrap[T](stub: Stub[T], whoBlame: Who): Gift[T] = {
val provide: Provide[T] = new Provide[T] {
override def apply(fillBox: FillBox[T]): Unit = {
val fill: Fill[T] = beMe(fillBox)
fill(stub)
}
}
whoBlame(provide)
}
private def unwrap[T](gs3: Gift[T], whoCarol: Who): Stub[T] = {
val provide: Provide[T] = beMe(gs3)
val result = new AtomicReference[Stub[T]]()
val fill: Fill[T] = new Fill[T] {
override def apply(s3: Stub[T]): Unit = result.set(s3)
}
val fillBox: FillBox[T] = whoCarol(fill)
provide(fillBox)
result.get()
}
class StubImpl[T](whoBlame: Who, delegate: T) extends Stub[T] {
def intro(whoBob: Who): Gift[T] = {
log(s"meet ${whoBob.hint}")
val stub = new StubImpl[T](whoBob, delegate)
wrap(stub, whoBob)
}
def deliver[ArgType](verb: String, desc: (Gift[ArgType], Who))
(implicit tTag: ru.TypeTag[T], cTag: ClassTag[T], proxyMaker: Principal.ProxyMaker[ArgType]): Any = {
log(s"$verb/1")
val (gift3, whoBlame) = desc
implicit val context: proxyMaker.Context = proxyMaker.Context(Principal.this, whoBlame, reportln)
val stub3: Stub[ArgType] = unwrap(gift3, whoBlame)
val proxy: Proxy[ArgType] = proxyMaker.makeProxy(stub3)
proxyAmps.put(proxy, (stub3, whoBlame))
// https://gist.github.com/bartschuller/4687387
val m = ru.runtimeMirror(delegate.getClass.getClassLoader)
val sym = ru.weakTypeTag[T].tpe.decl(ru.TermName(verb)).asMethod
val im = m.reflect(delegate)
val methodMirror = im.reflectMethod(sym)
methodMirror.apply(proxy)
}
override def toString: String = {
s"StubImpl(whoBlame = $whoBlame, delegate = $delegate, principal = $label)"
}
private def log(msg: String): Unit = {
reportln(s"${whoBlame.hint} asks me to:\n> > " + msg)
}
}
class ProxyAmpsImpl() extends ProxyAmps {
private val weakMap = mutable.WeakHashMap[AnyRef, (Stub[_], Who)]()
def put[T](key: T, value: (Stub[T], Who)): Unit = {
weakMap(key.asInstanceOf[AnyRef]) = value
}
def apply[T](key: T): (Stub[T], Who) = {
weakMap(key.asInstanceOf[AnyRef]).asInstanceOf[(Stub[T], Who)]
}
}
}
object Principal {
trait ProxyMaker[T] {
case class Context(principal: Principal, whoBlame: Who, reportln: String => Unit)
/**
* Implicitly converts from argument to a (Gift, Who) pair.
*/
implicit final def createDescription[A](argument: A)(implicit context: Context): (Gift[A], Who) = {
val (stubToIntro, whoFrom) = context.principal.proxyAmps(argument)
val gift = stubToIntro.intro(context.whoBlame)
(gift, whoFrom)
}
def makeProxy(stub: Stub[T])(implicit context: Context): Proxy[T]
}
}
Full source at GitHub