Viewing is open to everyone. Downloading and starring need a GitHub sign-in. Sign in with GitHub

CKey-Safe-Actions.md

v0.1.0-build.85 Latest Commit ede38f5

Size
8.9 KB
Lines
141
Language
markdown
SHA-256
b0f62584474c…
View on GitHub

Sign in with GitHub to download. Each download is recorded with your GitHub username.

1# CKey Safe Actions (draft 1)
2
3A **Safe Action** is a code that a CKey reader may act on **without asking first**. It is the one exception to the rule that a reader shows what a code holds and acts only on the user's approval ([payload conventions](CKey-Payload-Conventions.md), rule 1).
4
5The exception is narrow by design. A Safe Action is a signed request from a known site (the **issuer**) to a known app (the **receiving app**) to run one action that app has registered. The receiving app's SDK verifies it before anything happens, and anything that does not verify is ordinary text.
6
7Example: on `citeory.com`, a "Sign in with Viadere" code carries a Safe Action for the app `com.viadere`, action `signin.request`, signed by Citeory's key. Viadere's app verifies it and starts the sign-in.
8
9## What CKey does and does not vouch for
10
11The format, the verification rules and the official SDKs make sure an action is **genuine**: it came from the issuer it names, for the app and action it names, unchanged, once, and in time. CKey never vouches for **what** an action does. That is the issuer's and the receiving app's responsibility. openckey.org certifies how a code looks and scans, never its content ([trademark policy](https://openckey.org/trademark)).
12
13## Terms
14
15- **Issuer:** the site that creates and signs Safe Actions, named by its DNS name, for example `citeory.com`. It holds an Ed25519 private key on its own server and never shares it.
16- **Receiving app:** the app that runs the action, named by an app ID such as `com.viadere`. It registers which actions it accepts, from which issuers, and with which scope.
17- **Scope:**
18 - **same-device:** the result stays on the device that scanned, inside the receiving app. Examples: open a screen, look up an item, add something to a list.
19 - **cross-device:** the action approves something somewhere else, for example signing in a web browser on another computer.
20
21## Text form
22
23A Safe Action payload is UTF-8 text:
24
25```
26ckey-action:1:<envelope>
27```
28
29`<envelope>` is the envelope's bytes in base64url (RFC 4648 section 5) **without padding**, and any unused low bits of the final character are zero. A reader that does not support Safe Actions sees text that is not a link, so it shows it as text and nothing happens.
30
31## Envelope
32
33All integers are big-endian.
34
35| Field | Encoding | Rules |
36|---|---|---|
37| version | u8 | `1` |
38| scope | u8 | `0` same-device, `1` cross-device |
39| issuer | u8 length + bytes | DNS name, lower case `a-z 0-9 - .`, at least one dot, 1 to 253 bytes |
40| app | u8 length + bytes | `A-Z a-z 0-9 . _ -`, 1 to 255 bytes |
41| action | u8 length + bytes | `a-z 0-9 . _ -`, 1 to 64 bytes |
42| key_id | u8 length + bytes | `A-Z a-z 0-9 . _ -`, 1 to 32 bytes |
43| request_id | u8 length + bytes | `A-Z a-z 0-9 _ -`, 8 to 64 bytes, single use |
44| issued_at | u64 | Unix seconds |
45| expires_at | u64 | Unix seconds, later than `issued_at` |
46| params | u16 length + bytes | at most 1024 bytes; UTF-8 JSON is recommended |
47| signature | 64 bytes | Ed25519 (RFC 8032) |
48
49The signature covers the ASCII text `CKey Safe Action v1`, one zero byte, then every envelope byte before the signature. Verifiers reject a signature whose scalar S is not below the group order (RFC 8032 section 5.1.7). Nothing may follow the signature.
50
51## Issuer keys
52
53An issuer publishes its public keys at:
54
55```
56https://<issuer>/.well-known/ckey-safe-actions.json
57```
58
59```json
60{
61 "issuer": "citeory.com",
62 "keys": [
63 { "id": "2026-11", "alg": "Ed25519", "public_key": "<64 hex digits>", "not_after": "2027-11-01T00:00:00Z" }
64 ],
65 "request_info": "https://citeory.com/api/ckey/requests/{request_id}"
66}
67```
68
69- A key is bound to the issuer by where it is published: only whoever controls `citeory.com` can publish keys for it.
70- A receiving app may instead **pin** keys in its own code. Pinned keys are preferred for sign-in and payments.
71- To rotate a key, publish the new one beside the old, start signing with it, then remove the old one. To revoke a compromised key, remove it at once. Readers cache the file for at most one hour.
72- `request_info` is required for cross-device actions (below).
73
74## Request information (cross-device)
75
76For a cross-device action the reader shows a confirmation, and that confirmation must say **who is asking**. The reader fetches `request_info` with `{request_id}` replaced, over HTTPS, from the issuer's own domain:
77
78```json
79{
80 "request_id": "sess-41fd0a9be2c74",
81 "device": "Chrome on Windows",
82 "location": "near Lagos, Nigeria",
83 "requested_at": "2026-11-07T20:14:03Z",
84 "status": "pending"
85}
86```
87
88If it cannot be fetched, or its `status` is not `pending`, the action does not run.
89
90## What a reader must do
91
92A reader built on an official CKey SDK does exactly this; any other reader claiming Safe Actions support must do the same ([conformance](#conformance)).
93
941. A payload that does not start with `ckey-action:1:` follows the payload conventions unchanged.
952. Otherwise, parse it. Find the issuer's key by `issuer` and `key_id` among keys the receiving app pinned, or from the issuer's key file. No key: it is not a Safe Action.
963. Verify:
97 - the signature
98 - that `app` is this app
99 - that `action` is registered by this app, for this issuer
100 - that the lifetime is no longer than the app allows (default 10 minutes, at most 1 hour)
101 - that now is within it, with 2 minutes of clock tolerance
102 - that this reader has not seen the `request_id` before
1034. **Any check fails:** do nothing. Say that the code claims to be a Safe Action but could not be verified, with the reason. Do not offer to run it.
1045. **Same-device**, by both the envelope's scope and the app's registration: run the registered handler at once, with no prompt.
1056. **Cross-device**, by either the envelope's scope or the app's registration: the **SDK** shows a confirmation naming the verified issuer, the action, and the request information. The handler runs only after the user approves. The app cannot skip, replace or pre-approve this confirmation in an official SDK.
1067. The `request_id` is single use. The reader remembers it locally; the issuer's server enforces it as the authority.
107
108## What an issuer must do
109
110- Keep the private key on the server. Never put it in a page, an app, or a code.
111- Give every Safe Action a fresh `request_id`, and accept each one once.
112- Keep lifetimes short: a minute or two for sign-in, at most an hour for anything.
113- For cross-device actions, serve `request_info` and accept an approval only from the receiving app's backend, for a pending request, once.
114- Mark an action cross-device whenever its effect happens anywhere other than the scanning device.
115
116## Security notes
117
118- **Forgery:** without the private key nobody can make a valid envelope, or change an issuer, app, action, scope or parameter in one.
119- **Replay:** single-use request IDs and short lifetimes stop old codes and screenshots from working later.
120- **Relay:** someone can show a genuine cross-device code (for example a real sign-in code for *their own* browser session) somewhere you will scan it. A signature cannot detect that, because nothing in the code is fake. The cross-device confirmation, with the requesting device and place, is what lets a person notice. This is why cross-device actions are never fully automatic in the official SDKs.
121- **Same-device actions** cannot be relayed into someone else's session, because their result stays on the scanning device.
122
123## Unofficial auto-execution
124
125`ckey-unsafe-autoexec` is a separate package that parses and verifies Safe Actions, but leaves when and how to run them, and any prompt, to the developer. CKey does nothing unsafe in it: the developer takes on the responsibility the official SDK otherwise keeps. Most developers who use it want to own the prompt and still follow the rules above. Products built on it may say they read CKey codes, but may not claim CKey Safe Actions, certification, or that an action was brokered by CKey ([trademark policy](https://openckey.org/trademark)).
126
127## Conformance
128
129`tests/vectors/safe-actions-v1.json` holds envelopes made by an implementation independent of the C core (`tools/safe_actions_vectors.mjs`), each with the verdict a conforming verifier must reach: `ok`, `integrity` (bad signature), `expired` (out of time or too long-lived), or `invalid` (malformed). The core's tests check every one. A reader may describe itself as supporting CKey Safe Actions only if it reaches every verdict, and follows [what a reader must do](#what-a-reader-must-do).
130
131## Tools
132
133```sh
134ckey action keygen --out issuer.key
135ckey action public-key --key issuer.key
136ckey action sign --key issuer.key --issuer citeory.com --app com.viadere --action signin.request \
137 --key-id 2026-11 --request sess-41fd0a9be2c74 --scope cross-device --ttl 120
138ckey action verify --payload 'ckey-action:1:…' --public-key <hex>
139```
140
141The C ABI: `ckey_is_safe_action`, `ckey_safe_action_parse`, `ckey_safe_action_verify`, `ckey_safe_action_sign`, `ckey_ed25519_public_key` ([`ckey.h`](../include/ckey/ckey.h)).