These docs are under active development.
On this page
How-to 3 min read

Working with tokens

A token is the runtime carrier of an identity, and it is a file descriptor. This guide covers the everyday token tasks; token.h is the exhaustive reference for every call and field.

Who am I?

To inspect your own identity, open your effective token and query it:

int tok = peios_token_open_self(0, KACS_TOKEN_QUERY);
if (tok < 0) { /* errno */ }

unsigned char sid[PEIOS_SID_MAX_BYTES];
ssize_t n = peios_token_user(tok, sid, sizeof sid);   /* the user SID */

uint32_t il;
peios_token_integrity(tok, &il);                      /* integrity level RID */

struct peios_privilege_set privs;
peios_token_privileges(tok, &privs);                  /* held/enabled privileges */

close(tok);

peios_token_open_self gives you the effective token — if your thread is impersonating, that's the impersonated identity. Pass KACS_TOKEN_OPEN_REAL in the flags to get your process's real primary token regardless. The access argument is the handle rights you want; KACS_TOKEN_QUERY is enough to read.

Group SIDs and other list-valued classes come back as buffers you parse with the security.h views: read CLASS_GROUPS with peios_token_query, then peios_sid_array_parse it.

Who is calling me?

The most useful token trick in a service is learning the identity of whoever connected to your socket. When a client connects over a Unix stream or seqpacket socket, KACS captures their token at connect() time, and you open it from the accepted connection:

int conn = accept(listener, NULL, NULL);
int caller = peios_token_open_peer(conn);   /* QUERY | IMPERSONATE rights */
if (caller < 0) { /* errno */ }

/* Now query the caller's identity, or impersonate them (below). */

This is local authentication with no passwords and no handshake — the kernel vouches for who is on the other end. The handle comes with fixed QUERY | IMPERSONATE rights, which is exactly what a server needs.

Acting as the caller

Once you hold a caller's (impersonation) token, you can impersonate them: adopt their identity on your current thread so every subsequent access check runs as them, not as your service. This is how you do work on a client's behalf without running privileged and re-implementing their permissions.

if (peios_token_impersonate(caller) != 0) { /* errno */ }

/* ... do the work here: file opens, access checks, etc. all run as the caller ... */

peios_token_revert();   /* back to your own identity */
close(caller);

Always pair peios_token_impersonate with peios_token_revert, ideally in the cleanup path, so a failure partway through can't leave your thread wearing someone else's identity. peios_token_revert is a safe no-op if you weren't impersonating.

The full flow for a request handler is: acceptpeios_token_open_peerpeios_token_impersonate → serve the request → peios_token_revertclose.

Dropping power

To run less-trusted code with less authority than you hold, derive a restricted token and hand it over. peios_token_restrict can delete privileges, demote groups to deny-only, and add restricting SIDs:

struct peios_token_restrict spec = {
    .privs_to_delete = KACS_SE_DEBUG_PRIVILEGE | KACS_SE_IMPERSONATE_PRIVILEGE,
    .flags           = KACS_TOKEN_RESTRICT_WRITE_RESTRICTED,
};
int weak = peios_token_restrict(my_primary, &spec);

The result is a strictly less-powerful token. Combined with integrity levels and confinement (both set when minting a token), this is the basis of sandboxing on Peios.

Minting tokens

Creating a token from scratch requires SeCreateTokenPrivilege and is the province of authentication authorities, not ordinary programs. When you do need it, the token-spec builder is the ergonomic path — typed setters for the user SID, groups, privileges, integrity, claims, and the rest, then peios_token_builder_create. Mind the index convention for owner/primary-group references, and don't add the logon SID yourself — the kernel injects it.

Next