C10 Integration keys
- Area
- C — Trust & correctness
- Audience
- operator
- Kind
- feature
- Status
- accepted
- Maturity
- built
- Labels
- security, web
The feature in the specification · Read at
Requirements
An operator MUST be able to mint a named key with exactly one scope, read, act or member:<account>, at the command line and over the web API.
- locked by
- 0.18.0 met
- recorded done
- lemonfibercrates/lemonfiber-core/src/keys/run.rscrates/lemonfiber-core/src/keys.rscrates/lemonfiber-api/src/keys.rscrates/lemonfiber-api/src/admission.rscrates/lemonfiber-api/src/entitled.rs
- recorded done
- sdk-tssrc/client.tssrc/keys.test.ts::are minted with the password in the body, and the secret comes back once
- cited by
- lemonfiber@a20047c4c3lemonfiber-web@024caedc17lemonfiber-web@b4db1690e0sdk-ts@34b4705686
Over the web API, only an operator session MUST be able to mint or revoke a key, except as C10-R15 allows a member, and minting MUST require the operator's proof in the same request: the operator password, or a signature from a registered device key over a challenge the core issued for that request (C10-R24, C10-R25). A key MUST NOT be able to mint, list or revoke keys, whatever its scope.
A key's secret MUST appear only in the reply that minted it or, for a key minted as a claim, only in the reply that collected it (C10-R20), sent with Cache-Control: no-store, and MUST be stored only as a digest. No read, listing, log, support bundle, alert or journal entry MUST carry it.
- locked by
- 0.18.0 met
- recorded done
- lemonfibercrates/lemonfiber-core/src/keys/run.rscrates/lemonfiber-core/src/keys.rscrates/lemonfiber-api/src/keys.rscrates/lemonfiber-api/src/admission.rscrates/lemonfiber-api/src/entitled.rs
- cited by
- lemonfiber@41fdc1b377lemonfiber@a20047c4c3lemonfiber-web@024caedc17lemonfiber-companion@da105d58a3sdk-python@dffcdc092a
The mint reply MUST carry the stack's certificate pin beside the secret, and the stack's address where it has been served encrypted on the network. Where it has not, the reply MUST carry no address and MUST say how to serve the stack so another machine can connect.
A key MUST survive a restart and MUST travel in X-Lemonfiber-Token. A revoked, orphaned or unknown key MUST be refused exactly as a wrong token is, with the same status, sentence and code, from its next request.
Keys MUST be listed by name, scope, state, minting time and last use, without their secrets, and MUST be revocable by name, at the command line and over the web API.
- locked by
- 0.18.0 met
- recorded done
- lemonfibercrates/lemonfiber-core/src/keys/run.rscrates/lemonfiber-core/src/keys.rscrates/lemonfiber-api/src/keys.rscrates/lemonfiber-api/src/admission.rscrates/lemonfiber-api/src/entitled.rs
- recorded done
- sdk-tssrc/client.tssrc/keys.test.ts::are listed from the keys route, with the token as a headersrc/keys.test.ts::are revoked by name, sent as one path segment
- cited by
- lemonfiber@a20047c4c3lemonfiber-web@024caedc17sdk-ts@34b4705686sdk-php@8af096c45bsdk-php@81053d360e
A read key MUST be admitted to every served read and the event stream and to no action.
An act key MUST be admitted to what read admits and to exactly the actions the contract publishes as callable by a key. Any other action MUST be refused, naming the key's scope.
- locked by
- 0.18.0 met
- recorded done
- lemonfibercrates/lemonfiber-core/src/keys/run.rscrates/lemonfiber-core/src/keys.rscrates/lemonfiber-api/src/keys.rscrates/lemonfiber-api/src/admission.rscrates/lemonfiber-api/src/entitled.rs
- cited by
- lemonfiber@a20047c4c3sdk-ts@bb38134654sdk-php@192d7f4324sdk-python@1c593f02d6
The actions callable by a key MUST be published in the contract, each saying whether it disturbs the running system and whether it can be rehearsed. Uninstalling, any action on a credential, a key or the password, installing, updating or removing a plugin, a reset and a restore MUST NOT be callable by a key.
- locked by
- 0.18.0 met
- recorded done
- lemonfibercrates/lemonfiber-core/src/keys/run.rscrates/lemonfiber-core/src/keys.rscrates/lemonfiber-api/src/keys.rscrates/lemonfiber-api/src/admission.rscrates/lemonfiber-api/src/entitled.rs
- cited by
- lemonfiber@a20047c4c3sdk-ts@bb38134654sdk-php@192d7f4324sdk-python@1c593f02d6
A member:<account> key MUST be admitted to exactly what that account's own session admits, and MUST be refused once the account leaves the household.
A key MUST NOT be accepted from another machine except over the TLS its pin verifies. A key presented from another machine without it, or over a connection whose origin cannot be placed, MUST be refused before it is checked, with a refusal code of its own.
Every mint and revoke MUST be journaled and MUST raise an operator alert naming the key and its scope. A mint MUST be journaled as reversible, its undo revoking the key, and a revoke MUST be journaled as not reversible.
A presented value shaped like a key that matches no key MUST count against the same surface-wide limit as wrong passwords, and a revoked or orphaned key MUST NOT. While that limit holds, a request carrying a value shaped like a key MUST be refused with the wait before the value is checked, and a right key MUST NOT reset the count.
Minting a key under a name another key holds MUST be refused, naming that key.
A setting, LEMONFIBER_MEMBER_KEYS, off by default and changeable only by the operator, MUST decide whether household members may mint keys. While it is on, a member session MUST be able to mint a member: key scoped to that member alone, giving their own password again in the same request, checked by the media server and counted against the same limits as their sign-in, and MUST NOT be able to mint any other scope or a key for another account. While it is off, a member's mint MUST be refused with a refusal code of its own, and a member MUST still be able to revoke the keys scoped to them.
A member-minted key MUST be listed to the operator, marked as member-minted, and MUST be revocable by the operator as well as by the member. Its mint and revoke MUST be journaled and MUST alert the operator (C10-R12). A member's listing MUST show only the keys scoped to them, and a key that is not theirs MUST be answered to them as a key that does not exist.
Every key MUST carry a purpose chosen when it is minted, home-assistant, mcp or other, shown in the listing as the minter's declaration rather than as anything the core verified.
A key MUST be able to be minted as a claim, at the command line and over the web API. Its reply MUST carry no secret but a key link, lemonfiber://key? followed by these parameters, percent-encoded and no others: address, fingerprint, stack, expires and claim. The listing MUST show the key as waiting to be collected until it is. Where the reply could carry no address (C10-R4), a claim MUST NOT be minted, and the refusal MUST say how to serve the stack so another machine can connect.
- locked by
- 0.18.0 met
- recorded done
- lemonfibercrates/lemonfiber-core/src/keys/run/claimed.rscrates/lemonfiber-core/src/keys/claiming.rscrates/lemonfiber-core/src/companion.rscrates/lemonfiber/src/render/keys.rscrates/lemonfiber-api/tests/integration/what_a_claim_collects.rs
- recorded partial
- sdk-pythonsrc/lemonfiber/keys.pytests/test_keys.py::test_a_key_minted_as_a_claim_asks_for_one_and_is_answered_with_its_linktests/test_keys.py::test_a_key_link_is_read_as_a_claims_mint_writes_it
- cited by
- lemonfiber@081b2de990lemonfiber@41fdc1b377lemonfiber-web@7187c6e791sdk-ts@3af3f58b30sdk-python@dffcdc092aintegration-home-assistant@2f9050847aintegration-mcp@256c7e49da
The core MUST keep a claim only as a hash on the key it was minted for. A claim MUST collect its key once, and only within ten minutes of the mint; collecting, lapsing or the key's revocation MUST end it.
A key MUST be collected by a request carrying its claim alone, in the body, over the TLS the link's fingerprint verifies (C10-R11). The reply MUST carry the key's secret, name, scope and the stack's pin, sent with Cache-Control: no-store. An unknown, spent or lapsed claim MUST be refused with one status, sentence and code, and MUST count against the same surface-wide limit as wrong passwords (C10-R13).
- locked by
- 0.18.0 met
- recorded done
- lemonfibercrates/lemonfiber-api/src/keys.rscrates/lemonfiber-api/src/router.rscrates/lemonfiber-core/src/keys/run/claimed.rscrates/lemonfiber-api/tests/integration/what_a_claim_collects.rs
- recorded partial
- sdk-pythonsrc/lemonfiber/keys.pytests/test_keys.py::test_a_claim_collects_its_key_over_the_tls_its_link_pins_with_no_credentialtests/test_keys.py::test_a_claim_is_sent_nowhere_but_the_certificate_its_link_pins
- cited by
- lemonfiber@41fdc1b377lemonfiber-web@7187c6e791sdk-python@dffcdc092aintegration-home-assistant@2f9050847aintegration-mcp@256c7e49da
Collecting a key MUST be journaled and MUST raise an operator alert naming the key, its purpose and the address it was collected from. A key whose claim lapses uncollected MUST be revoked, journaled as not reversible, and alerted.
Every surface that shows a key link MUST also draw it as a QR code from the link, and MUST NOT show the link or the code again once that screen is left.
- locked by
- 0.18.0 met
- recorded done
- lemonfibercrates/lemonfiber/src/render/keys.rscrates/lemonfiber-core/src/keys/claiming.rs
- cited by
- lemonfiber@41fdc1b377
An operator session MUST be able to register a device key over the web API: a name, the public half of a P-256 key pair and the platform's attestation of where the key lives (an Android key attestation chain, or an iOS App Attest attestation whose client data hash is the SHA-256 of the public half's encoding, hashed once), with the operator password in the same request. The core MUST verify the attestation and record the key's backing as strongbox, enclave or tee, enclave meaning on iOS that App Attest vouched for the genuine app that placed the key there, not that the hardware proved it, and MUST refuse, each with a code of its own, a registration with no attestation, an attestation that does not verify or does not cover the public half, and an attestation placing the key in software. The core MUST keep only the public half and its backing under that name, MUST refuse a name or a public half another device key holds, naming it, and MUST refuse a public half that is not a P-256 key with a code of its own. A member session and a key MUST NOT be able to register, list or revoke device keys. Every registration MUST be journaled and MUST raise an operator alert naming the device key.
- locked by
- 0.18.0 met
- recorded done
- lemonfibercrates/lemonfiber-core/src/device_keys/run.rscrates/lemonfiber-core/src/device_keys/attestation.rscrates/lemonfiber-core/src/device_keys/attestation/android.rscrates/lemonfiber-core/src/device_keys/attestation/apple.rscrates/lemonfiber-core/src/device_keys/attestation/chain.rscrates/lemonfiber-core/src/device_keys/public.rscrates/lemonfiber-api/src/device_keys.rscrates/lemonfiber-core/src/device_keys/run/tests.rscrates/lemonfiber-api/tests/integration/what_a_device_key_proves.rs
- cited by
- lemonfiber@34b804b67e
An operator session MUST be able to ask for a challenge for a registered device key. The core MUST issue a fresh random value of at least 32 bytes, keep it only in memory, bound to that session and that device key, and accept it for one request within one minute; using it, its lapsing, the session ending and the device key's revocation MUST end it.
A mint carrying a device key's name, a challenge and a signature in place of the password MUST be admitted only where the device key is registered, the challenge stands for that session and that device key, and the signature verifies against the device key's public half over the challenge and the mint's name, scope, purpose and whether it is a claim, in the form the contract publishes. An unregistered or revoked device key MUST be refused with a code of its own; a challenge that does not stand, for any reason, with one code; and a signature that does not verify with a code of its own, counted against the same surface-wide limit as wrong passwords (C10-R13).
Device keys MUST be listed by name, the fingerprint of their public half, their backing, registration time and last use, and MUST be revocable by name, over the web API, with effect at the next challenge. Every revoke MUST be journaled as not reversible and MUST raise an operator alert naming the device key.
- locked by
- 0.18.0 uncited
- recorded done
- lemonfibercrates/lemonfiber-core/src/device_keys/run.rscrates/lemonfiber-core/src/device_keys/run/heard.rscrates/lemonfiber-api/src/device_keys.rscrates/lemonfiber-core/src/device_keys/run/tests.rscrates/lemonfiber-api/tests/integration/what_a_device_key_proves.rs
Changing the operator password MUST revoke every device key, journaled and alerted as C10-R26 says.
A device key's signature MUST prove the operator for the one mint it was made for and nothing else: it MUST NOT admit a request, stand in for a session or the password anywhere else, or be accepted without an operator session.
The core MUST register a device key only where its attestation names an app identity the core trusts: the package and the SHA-256 digest of its signing certificate on Android, the Team ID and bundle ID on iOS. The build MUST ship the official NightWorksIO companion's identities, and an attestation naming any other app MUST be refused with a code of its own, naming the identity. Until the companion's first store release the official list MUST be empty, so only the identities the operator adds are trusted, and the core release that follows the companion's first store release MUST ship its identities (releasing).
The operator MUST be able to add and remove the identities of their own builds as a setting, at the command line and over the web API (ARCH-R262); each change MUST be journaled and MUST raise an operator alert naming the identity, the official identities MUST NOT be removable, and removing an identity MUST revoke every device key registered from it.
The core MUST verify an attestation against the platform roots shipped in its build and MUST NOT send any request to verify one, a revocation list included.
Restoring a configuration backup MUST revoke every device key once the restore lands, each journaled as not reversible and alerted as C10-R26 says.
A key minted with a device key's signature MUST record that device key, and the listing MUST show it beside the key (ARCH-R293). Revoking a device key MUST NOT revoke the keys it proved, and its alert MUST name each active key it proved.
- locked by
- 0.18.0 uncited
- recorded done
- lemonfibercrates/lemonfiber-core/src/keys.rscrates/lemonfiber-core/src/keys/run.rscrates/lemonfiber-core/src/device_keys/run/heard.rscrates/lemonfiber-api/src/keys.rscrates/lemonfiber-core/src/device_keys/run/tests.rscrates/lemonfiber-api/tests/integration/what_a_device_key_proves.rs
The core MUST refuse, with a code of its own whose words name no key material, an Android key attestation any certificate of which carries a serial number that the build's snapshot of Google's attestation status list lists as revoked or suspended. Each build MUST embed that snapshot, taken at release time and recording where it was read from and when; the core MUST NOT fetch the list at run time (C10-R31), and MUST report when its snapshot was taken in the version it answers with (ARCH-R294, releasing).
- locked by
- 0.18.0 uncited
- recorded done
- lemonfibercrates/lemonfiber-core/src/device_keys/attestation/status.rscrates/lemonfiber-core/src/device_keys/attestation/roots/google-statusscripts/take_the_attestation_status.pycrates/lemonfiber-core/src/device_keys/attestation/android/tests.rscrates/lemonfiber-core/src/device_keys/attestation/status/tests.rs