Building capability-based data security for Ceramic

How we designed an access control system for sovereign data on a decentralized network.

Building capability-based data security for Ceramic

The 3Box Labs team recently published a new standard for creating capability containers for accessing decentralized data to the Chain Agnostic Standards Alliance. Capability containers are an approach for managing advanced data security and permissions, commonly referred to as “Object Capabilities” or “OCAPs.”

This new standard is currently in development for use on Ceramic. Once deployed in a future version of the protocol, it will allow Ceramic to be fully compatible with the new Sign-in with Ethereum (SIWE) specification as well as provide advanced data flow control features for resources stored on the Ceramic network. This post explains how it all works.

The need for more advanced access controls

For Ceramic, a decentralized data network, identity and access control are first-class considerations. A key question when managing user-centric data is, "As a user, how can I safely permit a third-party to act on my behalf?" Use cases could be as basic as allowing an application to read private information in my profile, or as complex as allowing an application to update my data sharing and permission settings. To make all of this work, Ceramic needs more advanced access control functionality to support all applications that involve data sharing, collaboration, teamwork, or other multi-user, data-driven features.

Most access control implementations follow one of two distinct approaches: Access-Control Lists (ACL) and Object Capability model (OCAP). ACL includes role-based access control, mandatory access control, and discretionary access control. This solution includes maintaining a list of known accounts along with their permissions.

On the other hand, OCAPs use capabilities as a means of managing access control rights without being locked-in to a specific account model or canonical ACL list. Ceramic, like other Web3 protocols, works in a decentralized and highly adversarial environment. This introduces risks and complexities beyond those found in centralized systems relating to DoS, the Ceramic code supply chain, the supply chain of the applications built on top of Ceramic, private key stealing/loss, a UI that misleads users, government actions, and finally protocol design flaws. As a permissionless and decentralized network, there’s also no centralized registry of roles and permissions that an ACL would require. Therefore, we believe object capabilities are the natural correct choice for designing an access control protocol that operates in a distributed environment.

Introduction to Capabilities

A capability is an unforgeable piece of data, that represents a right to operate on an object. It contains a list of allowed actions and restrictions on each object, together known as a caveat. When an entity that owns a capability issues another one, these capabilities form a chain. Every following capability in a chain contains a caveat that permits less or equal to the previous one.

You could think of a capability as a "power of attorney" document, but more powerful: the capability may include the right to delegate some of the permissions to yet another actor. In the figure above, we have three entities A, B, and C.

  • "A" issues a capability to "B" with the set alpha=[1,2,3], representing permitted actions on some resource.
  • "B" gives another capability to "C", based on its first capability.
  • Now, "B" attenuates (reduces) the permissions of "C" to beta=[1,2].
  • "C" then invokes an operation on a resource presenting both the capabilities.
  • "Resource" is able to check the entire capability chain to either allow or reject the invocation.

Our initial research on capabilities found two existing approaches for how to represent capabilities: zCAP-LD, based on the LD-Proof specification, and UCAN by Fission, which has no formal specification. Both share the same general structure; they describe who can perform what action with a cryptographic proof. In examining these options for Ceramic, we found that while both zCAP-LD and UCAN could be serialized as IPLD objects, they use signature schemes that are not easily compatible with the blockchain wallets primarily used for authentication amongst Ceramic community developers and throughout the broader Web3 ecosystem. Therefore, we decided to pull elements from each and create a new format, CACAO (Chain Agnostic CApability Object).

Capabilities on Ceramic

Today when a user opens an application that uses Ceramic to store and manage data, she is likely authenticated via 3ID-Connect. 3ID-Connect protects a user's private key and all the signing requests which go through it. For each Ceramic stream update, the following process occurs:

Whenever the app needs to update a stream it goes to an identity wallet—3ID-Connect—to request a signature for the update before applying it to the stream. The process repeats for all subsequent updates.

Instead, we would like an application to request a capability once for writing to a stream (or any other access-controlled resource) and proceed to do many updates. This is a critical UX improvement and needs to be done while maintaining security.

The corresponding key scheme looks as follows:

From a UX perspective, the flow resembles permissioning on a modern operating system. Before an iPhone application can access a protected resource, it has to ask for the user's permission.

Compared to an OS permissioning system, CACAO provides two critical advantages.

First, re-delegation and attenuation of permissions. This enables more sophisticated and powerful permissioning systems to be created. For example, users could grant rights to a data collective, privacy watchdog, or bank that they trust to manage permissions well. Or they could grant permission to a social application to further grant certain permissions (say, to read their private social graph) to other users on the app, thus trusting the application to create shared social experiences without compromising UX.

Second, CACAO gives users and other entities direct cryptographic control over their granted permissions, rather than relying on 3rd party enforcement (e.g., Apple’s code). Among other benefits, this means permissions can always be revoked. It also creates auditable and cryptographic guarantees that the correct permissions are being enforced, rather than finding out an application has abused your trust and granted permission to data where they weren’t supposed to (e.g. Facebook and Cambridge Analytica).

CACAO Standard & Implementation

As described in CAIP standard draft, we can represent the signed "Sign-in with Ethereum" (SIWE) payload as an IPLD object. We named it Chain Agnostic CApability Object or CACAO. This allows us to describe desired permissions as URIs (see the SIWE Resources section) in the SIWE payload and use the IPLD object directly as a capability.

type Payload struct {
  domain String // =domain
  iss String // = DID pkh
  aud String // =uri
  version String
  nonce String
  iat Int // Unix timestamp =issued-at
  nbf optional Int // Unix timestamp =not-before
  exp optional Int // Unix timestamp = expiration-time
  statement optional String // =statement
  requestId optional String // =request-id
  resources optional [ String ] // =resources as URIs

In Ceramic, data is stored in IPLD DAGs called streams. An update to a Ceramic stream is a DAG-JWS object called a commit, containing a cryptographic signature and the CID of the original payload. To leverage capabilities in this structure, we reference the target capability in the JWS header as a "cap" property. When verifying a new commit, we could see that a signer differs from a stream controller. In this scenario, we check if the controller has permitted the signer to perform the operation by validating the referenced capability in the JWS header.

{.p.domain} wants you to sign in with your Ethereum account:


URI: {.p.aud}
Version: {.p.version}
Chain ID: {.p.iss[chainId]}
Nonce: {.p.nonce}
Issued At: {.p.iat}
- {.p.resources[0]}
- {.p.resources[1]}
- {.p.resources[n]}

We thus achieve the goal of allowing an application to act on behalf of an account.

Next steps

This post outlined our approach to capability-based authorization in Ceramic, however there is still much work to be done before it is ready for a general audience. One particular challenge will be formalizing attenuations. Instead of relying on the sophisticated composition of "if" statements —which is a common practice — we may explore a domain-specific language to deal with any caveat uniformly with strict mathematical guarantees. Another challenge will be introducing the right UX patterns that responsibly give users direct control over their data and permissions.

Finally, we’ll be looking for the right specific use cases to benefit from capability-based access control to fully develop these use cases first. If you, your team, or your friends are interested in this kind of problem, do not hesitate to join our Discord and share your ideas. Let's see what we can build together!

Website | Twitter | Discord | GitHub | Documentation | Blog