Security In Scala - Lightbend · 2018-06-28 · Security In Scala Rened Types and Object...

50
Security In Scala Rened Types and Object Capabilities Will Sargent • @will_sargent https://tersesystems.com/ 1 / 50

Transcript of Security In Scala - Lightbend · 2018-06-28 · Security In Scala Rened Types and Object...

Page 1: Security In Scala - Lightbend · 2018-06-28 · Security In Scala Rened Types and Object Capabilities Will Sargent • @will_sargent ... OOP is all about accessibility through abstr

Security In ScalaRe�ned Types and Object Capabilities

Will Sargent • @will_sargent

https://tersesystems.com/

1 / 50

Page 2: Security In Scala - Lightbend · 2018-06-28 · Security In Scala Rened Types and Object Capabilities Will Sargent • @will_sargent ... OOP is all about accessibility through abstr

Many New Things!

2 / 50

Page 3: Security In Scala - Lightbend · 2018-06-28 · Security In Scala Rened Types and Object Capabilities Will Sargent • @will_sargent ... OOP is all about accessibility through abstr

What is Security in Scala?

3 / 50

Page 4: Security In Scala - Lightbend · 2018-06-28 · Security In Scala Rened Types and Object Capabilities Will Sargent • @will_sargent ... OOP is all about accessibility through abstr

Validate untrusted input.

Hide internal state and behavior.

Deny functionality to attackers.

4 / 50

Page 5: Security In Scala - Lightbend · 2018-06-28 · Security In Scala Rened Types and Object Capabilities Will Sargent • @will_sargent ... OOP is all about accessibility through abstr

Security DecisionsValidation - deciding what is valid.

Abstraction - deciding what should be visible.

Encapsulation - deciding what should be hidden.

Access Control - deciding what is denied.

5 / 50

Page 6: Security In Scala - Lightbend · 2018-06-28 · Security In Scala Rened Types and Object Capabilities Will Sargent • @will_sargent ... OOP is all about accessibility through abstr

Security ToolsRefinement Types - validation.

Capabilities - abstraction & encapsulation & revocable access.

Dynamic Sealing - encapsulation & information hiding.

Membranes - operational isolation & access control.

6 / 50

Page 7: Security In Scala - Lightbend · 2018-06-28 · Security In Scala Rened Types and Object Capabilities Will Sargent • @will_sargent ... OOP is all about accessibility through abstr

What are Re�nement Types?

7 / 50

Page 8: Security In Scala - Lightbend · 2018-06-28 · Security In Scala Rened Types and Object Capabilities Will Sargent • @will_sargent ... OOP is all about accessibility through abstr

Refinement Types are raw types that obey predicates.

8 / 50

Page 9: Security In Scala - Lightbend · 2018-06-28 · Security In Scala Rened Types and Object Capabilities Will Sargent • @will_sargent ... OOP is all about accessibility through abstr

Re�nement TypesExamples 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.

9 / 50

Page 10: Security In Scala - Lightbend · 2018-06-28 · Security In Scala Rened Types and Object Capabilities Will Sargent • @will_sargent ... OOP is all about accessibility through abstr

String Fail

10 / 50

Page 11: Security In Scala - Lightbend · 2018-06-28 · Security In Scala Rened Types and Object Capabilities Will Sargent • @will_sargent ... OOP is all about accessibility through abstr

Re�ned Libraryrefined: simple refinement types for Scala

libraryDependencies += "eu.timepit" %% "refined" % "0.9.0"

https://github.com/fthomas/refined

11 / 50

Page 12: Security In Scala - Lightbend · 2018-06-28 · Security In Scala Rened Types and Object Capabilities Will Sargent • @will_sargent ... OOP is all about accessibility through abstr

Beforeval requestParameter: String = "94111"

// XXX How do we know this is a zip code?val zipCode: String = requestParameter

12 / 50

Page 13: Security In Scala - Lightbend · 2018-06-28 · Security In Scala Rened Types and Object Capabilities Will Sargent • @will_sargent ... OOP is all about accessibility through abstr

Afterscala> import eu.timepit.refined._import eu.timepit.refined._

scala> import eu.timepit.refined.api.Refinedimport 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)

13 / 50

Page 14: Security In Scala - Lightbend · 2018-06-28 · Security In Scala Rened Types and Object Capabilities Will Sargent • @will_sargent ... OOP is all about accessibility through abstr

Re�nement Types AdvantagesYou 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!

14 / 50

Page 15: Security In Scala - Lightbend · 2018-06-28 · Security In Scala Rened Types and Object Capabilities Will Sargent • @will_sargent ... OOP is all about accessibility through abstr

What are Capabilities?

15 / 50

Page 16: Security In Scala - Lightbend · 2018-06-28 · Security In Scala Rened Types and Object Capabilities Will Sargent • @will_sargent ... OOP is all about accessibility through abstr

A capability is a security primitive that confers authority by reference.

Authority is sufficient justification to affect a resource.

16 / 50

Page 17: Security In Scala - Lightbend · 2018-06-28 · Security In Scala Rened Types and Object Capabilities Will Sargent • @will_sargent ... OOP is all about accessibility through abstr

De�ning Capabilities in ScalaIn 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."

17 / 50

Page 18: Security In Scala - Lightbend · 2018-06-28 · Security In Scala Rened Types and Object Capabilities Will Sargent • @will_sargent ... OOP is all about accessibility through abstr

Capability ExampleWe 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.

18 / 50

Page 19: Security In Scala - Lightbend · 2018-06-28 · Security In Scala Rened Types and Object Capabilities Will Sargent • @will_sargent ... OOP is all about accessibility through abstr

Document Beforefinal class Document(var name: String) { override def toString: String = s"Document($name)"}

val document = new Document("Steve")document.name = "Will"

19 / 50

Page 20: Security In Scala - Lightbend · 2018-06-28 · Security In Scala Rened Types and Object Capabilities Will Sargent • @will_sargent ... OOP is all about accessibility through abstr

Isolate Name as NameChangerfinal 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)"}

20 / 50

Page 21: Security In Scala - Lightbend · 2018-06-28 · Security In Scala Rened Types and Object Capabilities Will Sargent • @will_sargent ... OOP is all about accessibility through abstr

De�ne NameChangerobject Document { trait NameChanger { def changeName(name: String): Unit }

class Access { def nameChanger(doc: Document): NameChanger = { doc.capabilities.nameChanger } }}

21 / 50

Page 22: Security In Scala - Lightbend · 2018-06-28 · Security In Scala Rened Types and Object Capabilities Will Sargent • @will_sargent ... OOP is all about accessibility through abstr

Usageval document = new Document("Steve")// document: CapExample.Document = Document(Steve)

val access = new Document.Access()// access: CapExample.Document.Access = CapExample$Document$Access@577d1ce5

val nameChanger = access.nameChanger(document)// nameChanger: CapExample.Document.NameChanger = CapExample$Document$capabilities$

nameChanger.changeName("Will")

println(s"result = $document")// result = Document(Will)

22 / 50

Page 23: Security In Scala - Lightbend · 2018-06-28 · Security In Scala Rened Types and Object Capabilities Will Sargent • @will_sargent ... OOP is all about accessibility through abstr

Isolation of ObjectsThe nameChanger capability can affect the resource (Document object) bychanging its name.

Access to the resource (Document object) does not mean you have acapability on that resource.

The only way to get access to a NameChanger instance is throughDocument.Access, which is usually sealed.

Note that nameChanger is a "root level" capability! For delegation, youwould use Revocable.

23 / 50

Page 24: Security In Scala - Lightbend · 2018-06-28 · Security In Scala Rened Types and Object Capabilities Will Sargent • @will_sargent ... OOP is all about accessibility through abstr

More Advanced ExampleWe'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 theyneed"

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-hardprogammer.

24 / 50

Page 25: Security In Scala - Lightbend · 2018-06-28 · Security In Scala Rened Types and Object Capabilities Will Sargent • @will_sargent ... OOP is all about accessibility through abstr

ItemRepository Capabilitiesobject 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 }}

25 / 50

Page 26: Security In Scala - Lightbend · 2018-06-28 · Security In Scala Rened Types and Object Capabilities Will Sargent • @will_sargent ... OOP is all about accessibility through abstr

ItemRepository Capabilitiesclass 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) } }}

26 / 50

Page 27: Security In Scala - Lightbend · 2018-06-28 · Security In Scala Rened Types and Object Capabilities Will Sargent • @will_sargent ... OOP is all about accessibility through abstr

ItemRepository Usagescala> val repo: ItemRepository = new ItemRepository {}repo: RevokeExample.ItemRepository = $anon$1@1d3528e4

scala> val access = new ItemRepository.Access()access: RevokeExample.ItemRepository.Access = RevokeExample$ItemRepository$Access@

scala> val finder = access.finder(repo)finder: RevokeExample.ItemRepository.Finder[RevokeExample.Id] = RevokeExample$ItemR

scala> val maybeItem: Option[Item] = finder.find(ID)maybeItem: Option[RevokeExample.Item] = Some(Item(c31d34e2-5892-4a2d-9fd5-3ce2e0efe

27 / 50

Page 28: Security In Scala - Lightbend · 2018-06-28 · Security In Scala Rened Types and Object Capabilities Will Sargent • @will_sargent ... OOP is all about accessibility through abstr

ItemRepository Usage with E�ectsscala> 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: I | Try(access.updater(repo).update(item)) | } | }defined class TryAccess

scala> val tryAccess = new TryAccess(access)tryAccess: TryAccess = TryAccess@5373d34e

scala> // Create a finder which uses `Try` as an effect | val tryFinder = tryAccess.finder(repo)tryFinder: RevokeExample.ItemRepository.Finder[scala.util.Try] = TryAccess$$Lambda$

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(c31d34

28 / 50

Page 29: Security In Scala - Lightbend · 2018-06-28 · Security In Scala Rened Types and Object Capabilities Will Sargent • @will_sargent ... OOP is all about accessibility through abstr

RevocationRevocation takes back the ability to use a capability.

ocaps* implements macros and utility classes for revocation.

ocaps.Revocable - contains proxy that forwards to real capability andRevoker.

ocaps.Revoker - Signals to proxy that capability is revoked when revoke()is called.

* https://wsargent.github.io/ocaps/

29 / 50

Page 30: Security In Scala - Lightbend · 2018-06-28 · Security In Scala Rened Types and Object Capabilities Will Sargent • @will_sargent ... OOP is all about accessibility through abstr

Revocable Example

scala> // Call find as usual... | revocableFinder.find(ID) // throws RevokedException!ocaps.RevokedException: Capability revoked!

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[IdrevocableFinder: RevokeExample.ItemRepository.Finder[RevokeExample.Id] = $anon$1@3drevoker: ocaps.Revoker = ocaps.LatchRevoker@56b72d71

scala> // Call the revoker, which causes forwarding to stop... | revoker.revoke()

30 / 50

Page 31: Security In Scala - Lightbend · 2018-06-28 · Security In Scala Rened Types and Object Capabilities Will Sargent • @will_sargent ... OOP is all about accessibility through abstr

ocaps macros for working with capabilitiesDeals with the boilerplate of creating proxies with well-known semantics.

import ocaps.macros._

Composition: compose two capabilities together.

val fooWithBar: Foo with Bar = compose[Foo with Bar](foo, bar)

Attenuation: extract a capability from a composed one.

val foo: Foo = attenuate[Foo](fooWithBar)

Modulation: set up before/after hooks on capability.

val modulated: Foo = modulate[Foo](revocableDoer, beforeFunc,afterFunc)

Expiration: use modulation/revocation for time/use based capabilities.

31 / 50

Page 32: Security In Scala - Lightbend · 2018-06-28 · Security In Scala Rened Types and Object Capabilities Will Sargent • @will_sargent ... OOP is all about accessibility through abstr

Expiration Examapleval deadline = duration.fromNowval 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)

32 / 50

Page 33: Security In Scala - Lightbend · 2018-06-28 · Security In Scala Rened Types and Object Capabilities Will Sargent • @will_sargent ... OOP is all about accessibility through abstr

What is Dynamic Sealing?

33 / 50

Page 34: Security In Scala - Lightbend · 2018-06-28 · Security In Scala Rened Types and Object Capabilities Will Sargent • @will_sargent ... OOP is all about accessibility through abstr

Wrapping an object in another class so that it cannot be seen or tamperedwith until it is unsealed.

34 / 50

Page 35: Security In Scala - Lightbend · 2018-06-28 · Security In Scala Rened Types and Object Capabilities Will Sargent • @will_sargent ... OOP is all about accessibility through abstr

Dynamic SealingDynamic sealing is a very old idea* that has a number of interesting uses.

The Morning Paper has a great writeup with a better explanation ofsealing 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.

* Protection in programming languages, Morris Jr., CACM 1973

35 / 50

Page 36: Security In Scala - Lightbend · 2018-06-28 · Security In Scala Rened Types and Object Capabilities Will Sargent • @will_sargent ... OOP is all about accessibility through abstr

Dynamic Sealingscala> import ocaps._import ocaps._

scala> val (sealer, unsealer) = Brand.create[String]("string hint").tuplesealer: ocaps.Brand.Sealer[String] = ocaps.Brand$$Lambda$5237/981202539@6c7bd074unsealer: ocaps.Brand.Unsealer[String] = ocaps.Brand$$Lambda$5238/1024427149@836498

scala> val sealedBox = sealer("foo")sealedBox: ocaps.Brand.Box[String] = <sealed string hint box>

scala> val stringIsFoo = unsealer(sealedBox)stringIsFoo: Option[String] = Some(foo)

36 / 50

Page 37: Security In Scala - Lightbend · 2018-06-28 · Security In Scala Rened Types and Object Capabilities Will Sargent • @will_sargent ... OOP is all about accessibility through abstr

Dynamic Sealing UsesSeal credentials to avoid accidental leakage through logging.

Safely transport capabilities, Access, database connections in Akkamessages!

Signing / Trademarks - make the unsealer publicly available.

Encryption - make the sealer publicly available.

37 / 50

Page 38: Security In Scala - Lightbend · 2018-06-28 · Security In Scala Rened Types and Object Capabilities Will Sargent • @will_sargent ... OOP is all about accessibility through abstr

Dynamic Sealing Advanced Examaplescala> 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[Food]) { | def open(can: Can): Food = unsealer(can.food).get | }defined class CanOpener

scala> val (sealer, unsealer) = Brand.create[Food]("canned food").tuplesealer: ocaps.Brand.Sealer[Food] = ocaps.Brand$$Lambda$5237/981202539@347bd6beunsealer: ocaps.Brand.Unsealer[Food] = ocaps.Brand$$Lambda$5238/1024427149@791e0ce

scala> val canOpener = new CanOpener(unsealer)canOpener: CanOpener = CanOpener@9a6ad8

scala> val canOfSpam: Can = Can(sealer(Food("spam")))canOfSpam: Can = Can(<sealed canned food box>)

scala> canOpener.open(canOfSpam)res10: Food = Food(spam)

38 / 50

Page 39: Security In Scala - Lightbend · 2018-06-28 · Security In Scala Rened Types and Object Capabilities Will Sargent • @will_sargent ... OOP is all about accessibility through abstr

What are Membranes?

39 / 50

Page 40: Security In Scala - Lightbend · 2018-06-28 · Security In Scala Rened Types and Object Capabilities Will Sargent • @will_sargent ... OOP is all about accessibility through abstr

An extension of a "forwarder" that transitively imposes a "policy" on allreferences exchanged via the membrane.

40 / 50

Page 41: Security In Scala - Lightbend · 2018-06-28 · Security In Scala Rened Types and Object Capabilities Will Sargent • @will_sargent ... OOP is all about accessibility through abstr

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 thinkyou're working with A, but you're really working with Future[A].

Origins in E, Javascript, literally a footnote in PhD thesis.

41 / 50

Page 42: Security In Scala - Lightbend · 2018-06-28 · Security In Scala Rened Types and Object Capabilities Will Sargent • @will_sargent ... OOP is all about accessibility through abstr

Membrane BackgroundAll arguments and methods are "wrapped" with membrane and cannotescape.

Commonly used in sandboxes for "uncooperative revocation".

Tough to do directly in JVM, using ByteBuddy interceptors may bepossible.

Type safety doesn't mix well with message passing.

Probably possible to do using Akka actors, if you got rid of actor selection.

42 / 50

Page 43: Security In Scala - Lightbend · 2018-06-28 · Security In Scala Rened Types and Object Capabilities Will Sargent • @will_sargent ... OOP is all about accessibility through abstr

Permeable MembraneHowever. If you squint, a membrane wrapper is totally a dependentlytyped monad.

if you are willing to deal with the effect up front, assume "cooperativerevocation"...

...then PermeableMembrane is for you.

ocaps provides PermeableMembrane and RevokerMembrane out of the box.

43 / 50

Page 44: Security In Scala - Lightbend · 2018-06-28 · Security In Scala Rened Types and Object Capabilities Will Sargent • @will_sargent ... OOP is all about accessibility through abstr

Singleton Object De�nitionobject Location { trait LocaleReader[F[_]] { def locale: F[Locale] }

trait TimeZoneReader[F[_]] { def timeZone: F[TimeZone] }}

44 / 50

Page 45: Security In Scala - Lightbend · 2018-06-28 · Security In Scala Rened Types and Object Capabilities Will Sargent • @will_sargent ... OOP is all about accessibility through abstr

Class De�nitionclass 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 } }}

45 / 50

Page 46: Security In Scala - Lightbend · 2018-06-28 · Security In Scala Rened Types and Object Capabilities Will Sargent • @will_sargent ... OOP is all about accessibility through abstr

PermeableMembrane continuedclass 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) } } }}

46 / 50

Page 47: Security In Scala - Lightbend · 2018-06-28 · Security In Scala Rened Types and Object Capabilities Will Sargent • @will_sargent ... OOP is all about accessibility through abstr

PermeableMembrane Usageval m = RevokerMembrane()// m: ocaps.RevokerMembrane = ocaps.RevokerMembrane@16ff8bac

val location = new Location(Locale.US, TimeZone.getTimeZone("PST"))// location: MembraneExample.Location = MembraneExample$Location@53c05739

val access = new MembraneAccess(new Location.Access(), m)// access: MembraneExample.MembraneAccess = MembraneExample$MembraneAccess@a893610

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$

format.get// res11: String = Wednesday, June 20, 2018 9:21:39 AM PDT

47 / 50

Page 48: Security In Scala - Lightbend · 2018-06-28 · Security In Scala Rened Types and Object Capabilities Will Sargent • @will_sargent ... OOP is all about accessibility through abstr

Failure is always an option

format2.get// ocaps.RevokedException: Capability revoked!

val m2 = RevokerMembrane()// m2: ocaps.RevokerMembrane = ocaps.RevokerMembrane@5d464093

val access2 = new MembraneAccess(new Location.Access(), m2)// access2: MembraneExample.MembraneAccess = MembraneExample$MembraneAccess@2d18989

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$$ano

m2.revoke()

48 / 50

Page 49: Security In Scala - Lightbend · 2018-06-28 · Security In Scala Rened Types and Object Capabilities Will Sargent • @will_sargent ... OOP is all about accessibility through abstr

Questions!

49 / 50

Page 50: Security In Scala - Lightbend · 2018-06-28 · Security In Scala Rened Types and Object Capabilities Will Sargent • @will_sargent ... OOP is all about accessibility through abstr

Thanks!Documentation at https://wsargent.github.io/ocaps/

Guide at https://wsargent.github.io/ocaps/guide/index.html

50 / 50