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.

sourceobject 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()
  }

}

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:

sourcepackage 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
}
sourceclass 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]
  }

}
The source code for this page can be found here.