class: center, middle # Security In Scala ### Refined Types and Object Capabilities Will Sargent • `@will_sargent` https://tersesystems.com/ ??? --- class: center, middle # Many New Things! --- class: center, middle, inverse # What is Security in Scala? --- class: center, middle Validate untrusted input. Hide internal state and behavior. Deny functionality to attackers. --- ## Security Decisions - Validation - deciding what is valid. - Abstraction - deciding what should be visible. - Encapsulation - deciding what should be hidden. - Access Control - deciding what is denied. ??? If something is invalid, it can't be used. If something is visible, then why? If something is hidden, then it can't be exposed. Access Control is obvious. --- ## Security Tools - Refinement Types - validation. - Capabilities - abstraction & encapsulation & revocable access. - Dynamic Sealing - encapsulation & information hiding. - Membranes - operational isolation & access control. --- class: center, middle, inverse # What are Refinement Types? --- class: center, middle Refinement Types are raw types that obey predicates. --- ## Refinement Types - Examples of raw types: `String`, `Boolean`, `Character`, `Int`, Collection API. - Raw types are unconstrained, and could contain invalid or harmful input. - In particular, String can contain **anything**. --- ## String Fail .center[![string.png](string.png)] --- ## Refined Library - refined: simple refinement types for Scala - `libraryDependencies += "eu.timepit" %% "refined" % "0.9.0"` - [https://github.com/fthomas/refined](https://github.com/fthomas/refined) --- ## Before ```scala val requestParameter: String = "94111" // XXX How do we know this is a zip code? val zipCode: String = requestParameter ``` --- ## After ```scala scala> import eu.timepit.refined._ import eu.timepit.refined._ scala> import eu.timepit.refined.api.Refined import eu.timepit.refined.api.Refined scala> import eu.timepit.refined.string._ import eu.timepit.refined.string._ scala> import eu.timepit.refined.auto._ import eu.timepit.refined.auto._ scala> type ZipCode = String Refined MatchesRegex[W.`"""\\d{5}"""`.T] defined type alias ZipCode scala> val requestParameter: String = "94111" requestParameter: String = 94111 scala> val v: Either[String, ZipCode] = refineV(requestParameter) v: Either[String,ZipCode] = Right(94111) ``` --- ## Refinement Types Advantages - You can't get a `ZipCode` without going through `refineV`. - You can pass a `ZipCode` around anywhere you would use a `String`. - Move from "Stringly Typed" code to "Strongly Typed" code! --- class: center, middle, inverse # What are Capabilities? --- class: center, middle A capability is a security primitive that confers authority by reference. Authority is sufficient justification to affect a resource. ??? .footnote[.red[*] [Permission and Authority Revisited: towards a formalization](https://ai.google/research/pubs/pub45570)] --- ## Defining Capabilities in Scala - In Scala: - A resource is an object with some sensitive fields and/or methods. - A capability is a reference to an object that can affect the resource. - Distinct from OOP! * OOP is all about accessibility through abstraction. * "Here is a `Cake` with an `eat` method!" * Capabilities are all about inaccessability through encapsulation. * "If you don't have access to the `Cake`, you can't `eat` the cake." --- ## Capability Example - We have a `Document` object, which has a `name` field anyone can change. - We want to expose the ability to change the name as a capability. --- ## Document Before ```scala final class Document(var name: String) { override def toString: String = s"Document($name)" } val document = new Document("Steve") document.name = "Will" ``` --- ## Isolate Name as NameChanger ```scala final class Document(private var name: String) { private object capabilities { val nameChanger = new Document.NameChanger { override def changeName(newName: String): Unit = { name = newName } } } override def toString: String = s"Document($name)" } ``` --- ## Define NameChanger ```scala object Document { trait NameChanger { def changeName(name: String): Unit } class Access { def nameChanger(doc: Document): NameChanger = { doc.capabilities.nameChanger } } } ``` --- ## Usage ```scala val document = new Document("Steve") // document: CapExample.Document = Document(Steve) val access = new Document.Access() // access: CapExample.Document.Access = CapExample$Document$Access@f804c8d val nameChanger = access.nameChanger(document) // nameChanger: CapExample.Document.NameChanger = CapExample$Document$capabilities$$anon$1@4c8db1f6 nameChanger.changeName("Will") println(s"result = $document") // result = Document(Will) ``` --- ## Isolation of Objects - The `nameChanger` capability can affect the resource (`Document` object) by changing its name. - The only way to get access to a `NameChanger` instance is through `Document.Access`, which is usually sealed/`private`. - You can have a reference to the resource, and still not have ability to change name on the resource! - Note that `nameChanger` is a "root level" capability -- if you have access and you delegate, you would use `Revocable`. ??? We'll get to what "sealed" and "revocable" means in a bit. --- ## More Advanced Example - We've got a standard CRUD `ItemRepository` object as the resource. - By definition, everyone has access to create, read, update, and delete! - [Principle of Least Authority]() (POLA) says "only give people the power they need" - If an operation has no need of delete functionality, then don't hand it out. - Also, let's make functional effects part of the API, because we are `Try`-hard programmer. --- ## ItemRepository Capabilities ```scala object ItemRepository { trait Finder[F[_]] { def find(id: UUID): F[Option[Item]] } trait Updater[F[_]] { def update(item: Item): F[UpdateResult] } case class UpdateResult(message: String) class Access { def finder(repo: ItemRepository): Finder[Id] = repo.capabilities.finder def updater(repo: ItemRepository): Updater[Id] = repo.capabilities.updater } } ``` --- ## ItemRepository Capabilities ```scala class ItemRepository { import ItemRepository._ private def find(id: UUID): Option[Item] = items.find(_.id == id) private def update(u: Item): UpdateResult = UpdateResult(s"item $u updated") private object capabilities { val finder: Finder[Id] = new Finder[Id]() { override def find(id: UUID) = ItemRepository.this.find(id) } val updater: Updater[Id] = new Updater[Id]() { override def update(item: Item) = ItemRepository.this.update(item) } } } ``` --- ## ItemRepository Usage ```scala scala> val repo: ItemRepository = new ItemRepository {} repo: RevokeExample.ItemRepository = $anon$1@4f612be7 scala> val access = new ItemRepository.Access() access: RevokeExample.ItemRepository.Access = RevokeExample$ItemRepository$Access@73330331 scala> val finder = access.finder(repo) finder: RevokeExample.ItemRepository.Finder[RevokeExample.Id] = RevokeExample$ItemRepository$capabilities$$anon$1@3a70dd33 scala> val maybeItem: Option[Item] = finder.find(ID) maybeItem: Option[RevokeExample.Item] = Some(Item(c31d34e2-5892-4a2d-9fd5-3ce2e0efedf7,item name)) ``` --- ## ItemRepository Usage with Effects ```scala scala> import scala.util._ import scala.util._ scala> class TryAccess(access: ItemRepository.Access) { | def finder(repo: ItemRepository): ItemRepository.Finder[Try] = (id: UUID) => { | Try(access.finder(repo).find(id)) | } | def updater(repo: ItemRepository): ItemRepository.Updater[Try] = (item: Item) => { | Try(access.updater(repo).update(item)) | } | } defined class TryAccess scala> val tryAccess = new TryAccess(access) tryAccess: TryAccess = TryAccess@233512e3 scala> // Create a finder which uses `Try` as an effect | val tryFinder = tryAccess.finder(repo) tryFinder: RevokeExample.ItemRepository.Finder[scala.util.Try] = TryAccess$$Lambda$9768/4789483@9965d96 scala> // Now we know it won't throw exceptions :-) | val tryMaybeItem: Try[Option[Item]] = tryFinder.find(ID) tryMaybeItem: scala.util.Try[Option[RevokeExample.Item]] = Success(Some(Item(c31d34e2-5892-4a2d-9fd5-3ce2e0efedf7,item name))) ``` --- ## Revocation *Revocation* takes back the ability to use a capability. - `ocaps`.red[*] implements macros and utility classes for revocation. - `ocaps.Revocable` - contains proxy that forwards to real capability and `Revoker`. - `ocaps.Revoker` - Signals to proxy that capability is revoked when `revoke()` is called. .footnote[.red[*] [https://wsargent.github.io/ocaps/](https://wsargent.github.io/ocaps/)] --- ## Revocable Example ```scala scala> import ocaps._ import ocaps._ scala> import ocaps.macros._ import ocaps.macros._ scala> // Create proxy finder that will forward "find" method to `finder` | // revocableFinder is also called a "caretaker" | val Revocable(revocableFinder, revoker) = revocable[ItemRepository.Finder[Id]](finder) revocableFinder: RevokeExample.ItemRepository.Finder[RevokeExample.Id] = $anon$1@10b879f1 revoker: ocaps.Revoker = ocaps.LatchRevoker@b3c87a8 scala> // Call the revoker, which causes forwarding to stop... | revoker.revoke() ``` ```scala scala> // Call find as usual... | revocableFinder.find(ID) // throws RevokedException! ocaps.RevokedException: Capability revoked! ``` --- ## ocaps macros for working with capabilities * Deals with the boilerplate of creating proxies with well-known semantics. * `import ocaps.macros._` * *[Composition](https://wsargent.github.io/ocaps/guide/authorization.html#dispensing-capabilities-with-composition)*: compose two capabilities together. * `val fooWithBar: Foo with Bar = compose[Foo with Bar](foo, bar)` * *[Attenuation](https://wsargent.github.io/ocaps/examples/attenuation.html)*: extract a capability from a composed one. * `val foo: Foo = attenuate[Foo](fooWithBar)` * *[Modulation](https://wsargent.github.io/ocaps/guide/management.html#managing-behavior-with-modulation)*: set up before/after hooks on capability. * `val modulatedFoo: Foo = modulate[Foo](foo, beforeFunc, afterFunc)` *[Expiration](https://wsargent.github.io/ocaps/guide/management.html#managing-lifecycle-with-expiration)*: use modulation/revocation for time/use based capabilities. --- ## Expiration Example ```scala val deadline = duration.fromNow val Revocable(revocableDoer, revoker) = revocable[Doer](doer) val before: String => Unit = { _ => if (deadline.isOverdue()) { revoker.revoke() } } val after: (String, Any) => Unit = { (_, _) =>() } val expiringDoer: Doer = modulate[Doer](revocableDoer, before, after) ``` --- class: center, middle, inverse # What is Dynamic Sealing? --- class: center, middle Wrapping an object in another class so that it cannot be seen or tampered with until it is unsealed. --- ## Dynamic Sealing * Dynamic sealing is a very old idea.red[*] that has a number of interesting uses. * *The Morning Paper* has a [great writeup](https://blog.acolyer.org/2016/10/19/protection-in-programming-languages/) with a better explanation of sealing than the paper itself. * Two functions: `sealer` and `unsealer`, which wrap and unwrap a `Box`. * Like `default` scope, but if you got to pick your siblings. .footnote[.red[*] [Protection in programming languages, Morris Jr., CACM 1973](http://www.erights.org/history/morris73.pdf)] --- ## Dynamic Sealing ```scala scala> import ocaps._ import ocaps._ scala> val (sealer, unsealer) = Brand.create("hint").tuple sealer: ocaps.Brand.Sealer = Sealer(hashCode = 13316747, hint = hint) unsealer: ocaps.Brand.Unsealer = Unsealer(hashCode = 435436549, hint = hint) scala> val sealedBox = sealer("foo") sealedBox: ocaps.Brand.Box[String] = Box(hashCode = 399096427, hint = hint) scala> val stringIsFoo = unsealer(sealedBox) stringIsFoo: Option[String] = Some(foo) ``` --- ## Dynamic Sealing Uses * Seal credentials to avoid accidental leakage through logging. * Safely transport capabilities, `Access`, database connections in Akka messages! * Signing / Trademarks - make the unsealer publicly available. * Encryption - make the sealer publicly available. --- # Dynamic Sealing Advanced Examaple ```scala scala> case class Food(name: String) defined class Food scala> case class Can(food: Brand.Box[Food]) defined class Can scala> class CanOpener(unsealer: Brand.Unsealer) { | def open(can: Can): Food = unsealer(can.food).get | } defined class CanOpener scala> val (sealer, unsealer) = Brand.create("canned food").tuple sealer: ocaps.Brand.Sealer = Sealer(hashCode = 2073252515, hint = canned food) unsealer: ocaps.Brand.Unsealer = Unsealer(hashCode = 1631539842, hint = canned food) scala> val canOpener = new CanOpener(unsealer) canOpener: CanOpener = CanOpener@47c29159 scala> val canOfSpam: Can = Can(sealer(Food("spam"))) canOfSpam: Can = Can(Box(hashCode = 1751431289, hint = canned food)) scala> canOpener.open(canOfSpam) res10: Food = Food(spam) ``` --- class: center, middle, inverse # What are Membranes? --- class: center, middle An extension of a "forwarder" that transitively imposes a "policy" on all references exchanged via the membrane. ??? .footnote[[Trustworthy Proxies: Virtualizing Objects with Invariants](https://ai.google/research/pubs/pub40736) with alterations based on [Security Policies as Membranes in Systems for Global Computing](https://www.sciencedirect.com/science/article/pii/S1571066105051091/pdf?md5=7e42b14d1452cae3275d5d07fb260752&pid=1-s2.0-S1571066105051091-main.pdf&_valck=1)] --- ## Wait, what? * Okay, imagine you've got `Future[A]`, and you want a `Future[B]`. * `flatMap`, right? * A membrane provides `Future` and `flatMap` under the hood, so you think you're working with `A`, but you're really working with `Future[A]`. * Origins in E, Javascript, literally a footnote in PhD thesis. --- ## Membrane Background * All arguments and methods are "wrapped" with membrane and cannot escape. * Commonly used in sandboxes for "uncooperative revocation". * Tough to do directly in JVM, using ByteBuddy interceptors may be possible. * Type safety doesn't mix well with message passing. * Probably possible to do using Akka actors, if you got rid of actor selection. --- ## Permeable Membrane * **However.** If you squint, a membrane wrapper is totally a dependently typed monad. * if you are willing to deal with the effect up front, assume "cooperative revocation"... * ...then `PermeableMembrane` is for you. * `ocaps` provides `PermeableMembrane` and `RevokerMembrane` out of the box. --- ## Singleton Object Definition ```scala object Location { trait LocaleReader[F[_]] { def locale: F[Locale] } trait TimeZoneReader[F[_]] { def timeZone: F[TimeZone] } } ``` --- ## Class Definition ```scala class Location(locale: Locale, timeZone: TimeZone) { import Location._ private object capabilities { val localeReader: LocaleReader[Id] = new LocaleReader[Id] { override val locale: Locale = Location.this.locale } val timeZoneReader: TimeZoneReader[Id] = new TimeZoneReader[Id] { override val timeZone: TimeZone = Location.this.timeZone } } } ``` --- ## PermeableMembrane continued ```scala class MembraneAccess(access: Location.Access, val membrane: PermeableMembrane) { type Wrapper[+A] = membrane.Wrapper[A] def localeReader(location: Location): LocaleReader[Wrapper] = { new LocaleReader[Wrapper] { override def locale: Wrapper[Locale] = { membrane.wrap(access.localeReader(location).locale) } } } def timeZoneReader(location: Location): TimeZoneReader[Wrapper] = { new TimeZoneReader[Wrapper] { override def timeZone: Wrapper[TimeZone] = { membrane.wrap(access.timeZoneReader(location).timeZone) } } } } ``` --- # PermeableMembrane Usage ```scala val m = RevokerMembrane() // m: ocaps.RevokerMembrane = ocaps.RevokerMembrane@3e5efb79 val location = new Location(Locale.US, TimeZone.getTimeZone("PST")) // location: MembraneExample.Location = MembraneExample$Location@376fdb74 val access = new MembraneAccess(new Location.Access(), m) // access: MembraneExample.MembraneAccess = MembraneExample$MembraneAccess@3a509422 val format = for { timeZone <- access.timeZoneReader(location).timeZone locale <- access.localeReader(location).locale } yield { ZonedDateTime.now(timeZone.toZoneId) .format(DateTimeFormatter.ofLocalizedDateTime(FormatStyle.FULL) .withLocale(locale)) } // format: access.membrane.Wrapper[String] = ocaps.PermeableMembrane$Wrapper$$anon$1@77bbc60c format.get // res11: String = Saturday, July 21, 2018 2:07:48 PM PDT ``` --- ## Failure is always an option ```scala val m2 = RevokerMembrane() // m2: ocaps.RevokerMembrane = ocaps.RevokerMembrane@36f7c429 val access2 = new MembraneAccess(new Location.Access(), m2) // access2: MembraneExample.MembraneAccess = MembraneExample$MembraneAccess@7c035a0d val format2 = for { timeZone <- access2.timeZoneReader(location).timeZone locale <- access2.localeReader(location).locale } yield { ZonedDateTime.now(timeZone.toZoneId) .format(DateTimeFormatter.ofLocalizedDateTime(FormatStyle.FULL) .withLocale(locale)) } // format2: access2.membrane.Wrapper[String] = ocaps.PermeableMembrane$Wrapper$$anon$1@3647cd14 m2.revoke() ``` ```scala format2.get // ocaps.RevokedException: Capability revoked! ``` --- class: center, middle # Questions! --- class: center, middle # Thanks! Documentation at [https://wsargent.github.io/ocaps/](https://wsargent.github.io/ocaps/) Guide at [https://wsargent.github.io/ocaps/guide/index.html](https://wsargent.github.io/ocaps/guide/index.html)