Object Capabilities
Ceramic streams support CACAO, allowing a basic but powerful capability-based authorization system to exist. CACAO, or "chain agnostic object capabilities", are composable, transferable and verifiable containers for authorizations and encoded in IPLD. For the full CACAO specification and more examples, reference CAIP-74: CACAO - Chain Agnostic CApability Object.
Approach
Object capability-based authorization systems, of which CACAO is an implementation, are a natural way to represent authorizations in open and distributed systems. Object capabilities require little coordination, are only stored by parties that care about a particular capability, and are self-verifiable.
Contrast this to popular authorization models like access controls lists (ACLs), which often rely on the ability to maintain an accurate, agreed-upon, and up to date list of authorizations. ACLs are simple and sufficient when you can rely on a single authoritative source to maintain the list, but quickly become difficult in a distributed setting. Maintaining a list amongst many unknown participants becomes a difficult consensus problem and often very costly at scale, requiring lots of upfront and continuous coordination.
Usage
CACAO enables the ability for one account to authorize another account to construct signatures over limited data on their behalf, or in this case write to a Ceramic stream.
Using blockchain accounts
When combined with "Sign-in with X", CACAO unlocks the ability for blockchain accounts to authorize Ceramic accounts (DIDs) to sign data on their behalf.
This frequently used pattern in Ceramic greatly increases the the usability of user-owned data and public-key cryptography. Thanks to the adoption of blockchain based systems, many users now have the ability to easily sign data in web-based environments using their wallet and blockchain account.
Authorizing sessions
Data-centric systems like Ceramic often have more frequent writes than a blockchain system, so it can be impractical to sign every Ceramic stream event in a blockchain wallet. Instead with the use of CACAO and "Sign-in with X" many writes can be made by way of a temporary key and DID authorized with a CACAO. Allowing a user to only sign once with a blockchain based account and wallet, then continue to sign many payloads for an authorized amount of time (session).
In the future, we expect the ability to model the authorizations for more complex environments and structures including full organizations.
Specification
Support for object capabilities in the core Ceramic protocol is described below. Events in streams are signed payloads formatted in IPLD using DAGJWS (DAG-JOSE), as described here. This describes how this is extended to construct a valid signed payload using CACAO in DAGJWS, by example of first constructing a JWS with CACAO. A JWS with CACAO can then be directly encoded with DAG-JOSE after.
JWS with CACAO
JWS CACAO support includes adding a cap
parameter to the JWS Protected Header and specifying the correct kid
parameter string. Here is an example protected JWS header with CACAO:
{
"alg": "EdDSA",
"cap": "ipfs://bafyreidoaclgf2ptbvflwalfrr6d4iqehkzyidwbzaouprdbjjfb4yim6q"
"kid": "did:key:z6MkrBdNdwUPnXDVD1DCxedzVVBpaGi8aSmoXFAeKNgtAer8#z6MkrBdNdwUPnXDVD1DCxedzVVBpaGi8aSmoXFAeKNgtAer8"
}
Where:
alg
- identifies the cryptographic algorithm used to secure the JWScap
- maps to a URI string, expected to be an IPLD CID resolvable to a CACAO objectkid
- references the key used to secure the JWS. In the scope here this is expected to be a DID with reference to any key in the DID verification methods. The parameter MUST match theaud
target of the CACAO object for both the CACAO and corresponding signature to be valid together.
Since cap
is currently not a registered header parameter name in the IANA "JSON Web Signature and Encryption Header Parameters" registry, we treat this as a "Private Header Parameter Name" for now with additional meaning provided by the CACAO for implementations that choose to use this specification.
This means that ignoring the cap
header during validation will still result in a valid JWS payload by the key defined in the kid
. It just has no additional meaning by what is defined in the CACAO. The cap
header parameter could also have support added as an extension by using the crit
(Critical) Header Parameter in the JWS, but there is little reason to invalidate the JWS based on a consumer not understanding the cap
header given it is still valid.
DagJWS with CACAO
Construction
Given JWS with CACAO described in prior section, follow the DAG-JOSE specification and implementations for the steps to construct a given JWS with CACAO header and payload into a DagJWS. DagJWS is very similar to any JWS, except that the payload is a base64url encoded IPLD CID that references the JSON object payload.
{
cid: "bagcqcera2mews3mbbzs...quxj4bes7fujkms4kxhvqem2a",
value: {
jws: {
link: CID("bafyreidkjgg6bi4juwx...lb2usana7jvnmtyjb4xbgwl6e"),
payload: "AXESIGpJjeCjiaWv...LKw6pIDQfTVrJ4SHlwmsvx",
signatures: [
{
protected: "eyJhbGciOiJFZERTQSIsImNh...GU2djZEpLTmhYSDl4Rm9rdEFKaXlIQiJ9"
signature: "6usTYvu5KN0LFTQsWE9U-tqx...h60EgfvjL_rlAW7_tnQUl84sQyogpkLAQ"
}
]
}
}
}
Verification
The following algorithm describes the steps required to determine if a given DagJWS with CACAO is valid:
- Follow DAG-JOSE specification to transform a given DagJWS into a JWS.
- Follow JWS specifications to determine if the given JWS is valid. Verifying that the given signature paired with
alg
andkid
in the protected header is valid over the given payload. If invalid, an error MUST be raised. - Resolve the given URI in
cap
parameter of the projected JWS header to a CACAO JSON object. Follow the CAIP-74 CACAO specification to determine if the given CACAO is valid. If invalid, an error MUST be raised. - Ensure that the
aud
parameter of the CACAO payload is the same target as thekid
parameter in the JWS protected header. If they do not match, an error MUST be raised.
Example
Example IPLD dag-jose encoded block, strings abbreviated.
{
cid: "bagcqcera2mews3mbbzs...quxj4bes7fujkms4kxhvqem2a",
value: {
jws: {
link: CID("bafyreidkjgg6bi4juwx...lb2usana7jvnmtyjb4xbgwl6e"),
payload: "AXESIGpJjeCjiaWv...LKw6pIDQfTVrJ4SHlwmsvx",
signatures: [
{
protected: "eyJhbGciOiJFZERTQSIsImNh...GU2djZEpLTmhYSDl4Rm9rdEFKaXlIQiJ9"
signature: "6usTYvu5KN0LFTQsWE9U-tqx...h60EgfvjL_rlAW7_tnQUl84sQyogpkLAQ"
}
]
}
}
}
If block.value.jws.signatures[0].protected
is decoded, you would see the following object, a JWS protected header as described above:
{
"alg": "EdDSA",
"cap": "ipfs://bafyreidoaclgf...yidwbzaouprdbjjfb4yim6q",
"kid": "did:key:z6Mkq2ZyjGV54ev...hXH9xFoktAJiyHB#z6Mkq2ZyjGV54ev...hXH9xFoktAJiyHB"
}