Example Usage

Create Grant Capabilities

The most basic use of the CAProck library is to create capabilities to transmit to other processes or networked nodes.

Grantor

These capabilities usually grand access to some object, and objects usually don’t float freely in cyberspace, but have an “owner” of sorts - or someone who can make authoritative statements about who may have access. We’ll call this the issuer or grantor, and they’re represented by a cryptographic key pair.

Let’s generate an Edwards curve key, because they’re hip and small.

% openssl genpkey -algorithm ed25519 -out grantor.pem

For this example, it does not matter how you load this file. It’s sufficient that the library gets it as a string. Let’s hard-code it for the time being.

Warning

Do not hard-code key data in production code. It’s one of the most embarrassing security issues imaginable.

You don’t want to be embarrassed, do you?

So this is how it should not look in your code:

1char const * key = "-----BEGIN PRIVATE KEY-----...";

We can now create a key pair from this input data:

1#include <caprock/keys.h>
2
3caprock_key_pair kpair = caprock_key_init_pair();
4
5caprock_error_t err = caprock_key_read_pair(&kpair,
6   key, strlen(key), NULL);
7assert(CAPROCK_ERR_SUCCESS == err);

Grantee

Capabilities transfer rights to someone as well, which means it’s necessary to identify them (as the grantee). The assumption is that authentication also occurs via a cryptographic challenge based on public keys, which means we have at least a public key when we know a grantee.

We can create an identifier from such a public key.

 1caprock_key * pubkey = NULL;
 2
 3/* initialize pubkey much like the above */
 4
 5char grantee_buf[100];
 6size_t grantee_buf_size = sizeof(idbuf);
 7caprock_error_t err = caprock_key_identifier_public(
 8   CFI_CLAIMv1_SUBJECT,
 9   grantee_buf, &grantee_buf_size,
10   pubkey, CIH_ANY);
11assert(CAPROCK_ERR_SUCCESS == err);

Objects

Object identifiers don’t really have any specific format. However, identities can also be the object of a permission. For example, I could permit a person A to open the door to person B; in that case, the identifier for person B would be the object of a capability’s claim.

For that reason, it’s easiest to treat object identifiers as being in the same namespace as subject identifiers. Which means we can hash any arbitrary object name into an identifier.

1#include <caprock/basics.h>
2
3char object_buf[100];
4size_t object_buf_size = sizeof(idbuf);
5caprock_error_t err = caprock_create_object_id(
6   object_buf, &object_buf_size,
7   "test name", 0, CIH_512);
8assert(CAPROCK_ERR_SUCCESS == err);

Create Capability

Once we have the identities sorted, we need to create one or more claims about the grantees and objects. Note that each capability may contain several claims, but we’re only using a single one here.

The privilege or predicate is entirely application defined, so go wild! Use any string that expresses how you want to explain the relationship between the grantees and objects.

 1caprock_claim claim = {
 2   grantee_buf, grantee_buf_size,
 3   "my example privilege", 0, /* 0 here means to use strlen() */
 4   object_buf, object_buf_size,
 5};
 6
 7char capability[500];
 8size_t capability_size = sizeof(capability);
 9caprock_error_t err = caprock_grant_create(
10   capability, &capability_size,
11   kpair,
12   1, /* Sequence number */
13   "2023-06-26T16:51:00Z", /* from */
14   "2023-06-27T16:51:00Z", /* to */
15   CAPROCK_EXPIRY_POLICY_ISSUER,
16   &claim, 1,
17   CIH_ANY, CSA_AUTO);
18 assert(CAPROCK_ERR_SUCCESS == err);

That’s it.

Though it’s probably worth explaining some of the other parameters here.

  1. First of all, the sequence number applies to the issuer. That is, in order to determine in which sequence to process a number of capabilities by the same issuer, capabilities are typically sorted by sequence number. So every issuer needs to strictly increment this number for every capability created. If this reminds you how a certificate authority works, that’s no coincidence.

  2. The from and to parameters specify the capability’s validity period, expressed as ISO-8601 string dates.

  3. You will usually want to pass CAPROCK_EXPIRY_POLICY_ISSUER in the policy parameter.

  4. Pass a pointer to the first claim, and the number of claims to process next.

  5. Let the implementation choose a signature hash type with CIH_ANY.

  6. Let the implementation choose a signature algorithm with CSA_AUTO.

Create Revoke Capabilities

Creating a revoke capability follows the same logic as creating a grant capability. The only difference is that instead of using caprock_grant_create(), you instead call caprock_revocation_create().

Note that revocations typically negate a previously issued grant. This means the sequence number of a revocation will usually be higher than that of a corresponding grant. It’s possible to issue revocations before issuing a grant, but they don’t have any particular effect.

Warning

It’s also possible to create revocations for different time spans than grants, effectively “punching a hole” in a grant.

This is part of the design, but should not be abused.

Validate a Capability

Validating a capability involves checking whether its signature is correct. But it also asks whether the capability makes any kind of sense at the current point in time.

1char capability[] = { ... };
2caprock_error_t err = caprock_token_validate(
3   capabiltiy, sizeof(capability),
4   "2023-07-01T14:31:00Z",
5   public_key);
6assert(CAPROCK_ERR_SUCCESS == err);

The public key provided here must be that of an authority known to issue capabilities.

Validate a Claim

While validating a capability is certainly possible, the most common use case for capabilities is also somewhat more complex.

What you typically need to do is check whether a kind of access that was requested is permitted at the current point in time. From a CAProck perspective, you’re validating a claim against a set of capabilities.

Since the library does not know – or care to know – how you might store your capabilities, it offers an iterator construct for producing all the capabilities your software knows. You can use this iterator to e.g. query a database or some such.

 1caprock_error_t
 2my_iterator(void * buffer, size_t * bufsize, void * baton)
 3{
 4   // The baton contains a context for the iterator, such as e.g. a database
 5   // handle, etc.
 6   my_db * handle = (my_db *) baton;
 7   my_db->write_next_capability_to_buffer(buffer, bufsize);
 8   if (bufsize > 0) {
 9     return CAPROCK_ERR_SUCCESS;
10   }
11   return CAPROCK_ERR_END_ITERATION;
12}

The above code sample involves the use of some imaginary database handle that writes capabilities into an output buffer, provided to the iterator function. Of course, this is effectively pseudo-code, and you’ll have to provide your own implementation.

Suffice to know that the best iterator produces the capabilities in order of ascending sequence numbers. At the same time, the iterator can produce only those capabilities that are signed by the same authority; this is something of an optimization, however.

When you use the iterator, you typically deal with a request for some resource, specified by an object identifier. You’ll also know who is requesting access, from a prior authentication phase. Finally, the request provides you with information on how the resource access should occur. All of this together means you can construct a claim.

1caprock_claim request = {
2   subject, sizeof(subject),
3   predicate, sizeof(predicate),
4   object, sizeof(object),
5 };

We initialize the imaginary database query; in this case we query for capabilities related to the given object.

1 my_db * handle = query_db_with_caps_about(object);

What is left to do is to check whether the claim is permitted or forbidden by the set of capabilities that the iterator provides.

 1// We provide the scratch buffer to the function.
 2char tmp[1000];
 3
 4caprock_error_t err = caprock_claim_validate(
 5   &request, 1, // a single request/claim is checked
 6   "2023-07-02T07:38:12Z", // the current time
 7   tmp, sizeof(tmp),
 8   verifier_pubkey,
 9   CIF_NONE);
10assert(CAPROCK_ERR_SUCCESS == err);

On success, we can now state that the capabilities the iterator provides allow the resource access requested.