mirror of
https://github.com/block/buzz.git
synced 2026-10-27 04:30:52 +00:00
docs(nips): define agent-key assertions in NIP-FA and NIP-FI (#8384)
Defines how an enterprise identity adapter issues assertions for agent keys and how a NIP-FI relay admits them. - **NIP-FA:** adapters issue agent-key assertions only under an owner-authorized grant, created through the owner's own authentication; revoking the owner invalidates every earlier grant. The "agent keys" out-of-scope bullet is removed. - **NIP-FI:** new `Agent keys` subsection under the assertion contract (`nostr_owner_pubkey` claim, the verifier returns the owner, owner recording for the session or request, ban/deny checks on both keys, owner-derived authorization), `agent_assertions` discovery field, owner deny-set check in WebSocket and HTTP admission, owner-matching disconnect close scan, and the updated out-of-scope delegation lines. --------- Signed-off-by: Will Pfleger <pfleger.will@gmail.com> Co-authored-by: Hayt <211b96e6a2b7f45fd4047988976c7bbbeeda0c15f3ae7b32eec20834b5a55118@buzz.block.builderlab.xyz>
This commit is contained in:
@@ -132,6 +132,8 @@ Success response (`200`, `Cache-Control: no-store`):
|
|||||||
|
|
||||||
Adapters MUST issue dedicated [NIP-FI](NIP-FI.md) assertions whose protected `typ` is exactly `nip-fi+jwt` and that carry the NIP-FI required claims; as NIP-FI requires, `sub` is never an email address. The assertion's `aud` MUST be `https://` followed by the authority of the canonical `relay_url`, unchanged: `wss://community.example.com` maps to `https://community.example.com`, `wss://c.example.com:8443` to `https://c.example.com:8443`, and `wss://[2001:db8::1]:8443` to `https://[2001:db8::1]:8443`. This is the canonical host URI of the community the relay resolves from the connection's `Host`. The assertion's `nostr_pubkey` MUST be the key that signs the NIP-42 relay login; the relay rejects any other key. Clients MUST reject a response whose `nostr_pubkey` differs from the key sent in the request.
|
Adapters MUST issue dedicated [NIP-FI](NIP-FI.md) assertions whose protected `typ` is exactly `nip-fi+jwt` and that carry the NIP-FI required claims; as NIP-FI requires, `sub` is never an email address. The assertion's `aud` MUST be `https://` followed by the authority of the canonical `relay_url`, unchanged: `wss://community.example.com` maps to `https://community.example.com`, `wss://c.example.com:8443` to `https://c.example.com:8443`, and `wss://[2001:db8::1]:8443` to `https://[2001:db8::1]:8443`. This is the canonical host URI of the community the relay resolves from the connection's `Host`. The assertion's `nostr_pubkey` MUST be the key that signs the NIP-42 relay login; the relay rejects any other key. Clients MUST reject a response whose `nostr_pubkey` differs from the key sent in the request.
|
||||||
|
|
||||||
|
An adapter MUST NOT issue an assertion for an agent key through a user's own authentication. It MUST issue agent-key assertions only under an owner-authorized grant: a durable record, created through the owner's own authentication, that binds the agent key to the owner. A grant MAY be held by the agent as a credential or kept by the adapter for agents it operates. The adapter MUST issue only while the owner authorization recorded in the grant remains valid. Revoking the owner MUST invalidate every grant the owner authorized before the revocation, and a later authentication by the owner MUST NOT revalidate them. Agent-key assertions follow NIP-FI "Agent keys".
|
||||||
|
|
||||||
A client that rejects a `200` response, including one it cannot parse, MUST treat it as a refusal: keep the session, show it as refused, and not retry automatically.
|
A client that rejects a `200` response, including one it cannot parse, MUST treat it as a refusal: keep the session, show it as refused, and not retry automatically.
|
||||||
|
|
||||||
### Denials
|
### Denials
|
||||||
@@ -180,6 +182,5 @@ NIP-FI defines no public identity projection. Adapter responses are private logi
|
|||||||
|
|
||||||
## Out of scope
|
## Out of scope
|
||||||
|
|
||||||
- Assertions for agent keys. This NIP covers assertions for keys a user authenticates with directly; issuing assertions for agent keys requires a separate design.
|
|
||||||
- Discovery of which relays require enterprise identity and which adapter serves them.
|
- Discovery of which relays require enterprise identity and which adapter serves them.
|
||||||
- The upstream identity protocol between the adapter and its identity provider.
|
- The upstream identity protocol between the adapter and its identity provider.
|
||||||
|
|||||||
@@ -127,6 +127,30 @@ authority. Signature verification remains mandatory before acceptance.
|
|||||||
`VerifyAssertion` MUST compare `aud` with the resolved community's expected
|
`VerifyAssertion` MUST compare `aud` with the resolved community's expected
|
||||||
audience; issuer policy selection by `iss` MUST NOT broaden either binding.
|
audience; issuer policy selection by `iss` MUST NOT broaden either binding.
|
||||||
|
|
||||||
|
### Agent keys
|
||||||
|
|
||||||
|
An issuer MAY issue an assertion whose `nostr_pubkey` names an agent key
|
||||||
|
acting for an owner key. Such an assertion MUST carry `nostr_owner_pubkey`:
|
||||||
|
the lowercase hexadecimal encoding of one 32-byte public key, distinct from
|
||||||
|
`nostr_pubkey`. An issuer MUST NOT issue an assertion without
|
||||||
|
`nostr_owner_pubkey` for a key it has issued an agent assertion for, and MUST
|
||||||
|
NOT issue an agent assertion while the owner is not authorized by the issuer.
|
||||||
|
|
||||||
|
A relay advertising `agent_assertions: true` MUST verify `nostr_owner_pubkey`
|
||||||
|
as part of the assertion. A malformed value, or one equal to `nostr_pubkey`,
|
||||||
|
MUST deny `evidence_rejected`. The relay MUST record the value as the
|
||||||
|
owner of the session or request the assertion admits. If the relay already
|
||||||
|
associates a different owner with the asserted key in the resolved community,
|
||||||
|
it MUST deny `authorization_denied`.
|
||||||
|
For every agent assertion, regardless of whether the asserted key is itself
|
||||||
|
authorized in the community, the relay MUST deny when the community bans the
|
||||||
|
asserted key or the owner, or when the issuer has denied either key. Only
|
||||||
|
then, when the asserted key is not itself authorized in the community, the
|
||||||
|
relay MUST authorize it if and only if the issuer is trusted for the community
|
||||||
|
and the owner is authorized in the community when access is decided. A relay
|
||||||
|
not advertising `agent_assertions: true` MUST deny an assertion carrying
|
||||||
|
`nostr_owner_pubkey` `evidence_rejected`.
|
||||||
|
|
||||||
### Unverified claims
|
### Unverified claims
|
||||||
|
|
||||||
Relays MUST ignore claims they do not verify. An unverified claim — including
|
Relays MUST ignore claims they do not verify. An unverified claim — including
|
||||||
@@ -248,8 +272,14 @@ VerifyAssertion(token, community, D, R_t):
|
|||||||
AssertAudienceMatch(claims.aud, community.expected_aud) or DENY(evidence_rejected)
|
AssertAudienceMatch(claims.aud, community.expected_aud) or DENY(evidence_rejected)
|
||||||
AssertTimeBounds(claims, policy) or DENY(evidence_rejected) // [FI-TRACE-ASSERTION-VALIDATION]
|
AssertTimeBounds(claims, policy) or DENY(evidence_rejected) // [FI-TRACE-ASSERTION-VALIDATION]
|
||||||
k_claimed := ParseHexKey(claims.nostr_pubkey) or DENY(evidence_rejected)
|
k_claimed := ParseHexKey(claims.nostr_pubkey) or DENY(evidence_rejected)
|
||||||
|
owner := absent
|
||||||
|
if claims.nostr_owner_pubkey is present:
|
||||||
|
AssertAgentAssertionsAdvertised(relay) or DENY(evidence_rejected)
|
||||||
|
owner := ParseHexKey(claims.nostr_owner_pubkey) or DENY(evidence_rejected)
|
||||||
|
AssertNotEqual(owner, k_claimed) or DENY(evidence_rejected)
|
||||||
|
|
||||||
return VerifiedAssertion(identity=(claims.iss, claims.sub), asserted_key=k_claimed,
|
return VerifiedAssertion(identity=(claims.iss, claims.sub), asserted_key=k_claimed,
|
||||||
|
owner=owner,
|
||||||
authority_deadlines=ComputeDeadlines(claims, snapshot))
|
authority_deadlines=ComputeDeadlines(claims, snapshot))
|
||||||
```
|
```
|
||||||
|
|
||||||
@@ -275,15 +305,19 @@ On WebSocket upgrade:
|
|||||||
[FI-TRACE-ASSERTION-KEY-MISMATCH]
|
[FI-TRACE-ASSERTION-KEY-MISMATCH]
|
||||||
6. Register the session's proven `k` and admitting issuer `iss` in the
|
6. Register the session's proven `k` and admitting issuer `iss` in the
|
||||||
relay's session table, making it visible to the disconnect close scan.
|
relay's session table, making it visible to the disconnect close scan.
|
||||||
|
Registration includes the session's owner, if any.
|
||||||
Registration MUST occur before the deny-set check in step 7. This ordering
|
Registration MUST occur before the deny-set check in step 7. This ordering
|
||||||
ensures any connection that straddles a concurrent disconnect is caught by
|
ensures any connection that straddles a concurrent disconnect is caught by
|
||||||
one side or the other: either the close scan sees the registered session, or
|
one side or the other: either the close scan sees the registered session, or
|
||||||
the deny-set check (step 7) sees the inserted entry. Both checks use the
|
the deny-set check (step 7) sees the inserted entry. Both checks use the
|
||||||
same `(iss, k)` key. A concurrent disconnect close scan MAY cancel this
|
same keys: `(iss, k)` and, for a session with an owner,
|
||||||
registered connection before step 8; cancellation is terminal, so a
|
`(iss, verified.owner)`. A concurrent disconnect close scan MAY cancel
|
||||||
|
this registered connection before step 8; cancellation is terminal, so a
|
||||||
cancelled connection MUST NOT be admitted.
|
cancelled connection MUST NOT be admitted.
|
||||||
7. Check deny set for `(iss, k)`; active entry (`now < until`) → deny
|
7. Check the deny set for `(iss, k)` and, if `verified.owner` is present, for
|
||||||
`authorization_denied`. [FI-TRACE-DENY-SET]
|
`(iss, verified.owner)`. Either active entry (`now < until`) denies
|
||||||
|
`authorization_denied`.
|
||||||
|
[FI-TRACE-DENY-SET]
|
||||||
8. Admit the connection only if it remains live and uncancelled. The session's
|
8. Admit the connection only if it remains live and uncancelled. The session's
|
||||||
authority deadline is the minimum of all `authority_deadlines`; see Session
|
authority deadline is the minimum of all `authority_deadlines`; see Session
|
||||||
policy.
|
policy.
|
||||||
@@ -351,9 +385,10 @@ A disconnect call causes the relay to:
|
|||||||
capacity and a new entry cannot be inserted, the relay MUST reject the command
|
capacity and a new entry cannot be inserted, the relay MUST reject the command
|
||||||
`503`; no sessions are closed and no replay state is consumed.
|
`503`; no sessions are closed and no replay state is consumed.
|
||||||
2. Close all live WebSocket connections registered under the command's issuer
|
2. Close all live WebSocket connections registered under the command's issuer
|
||||||
whose proven `k` equals the target pubkey, whether or not admission has
|
whose proven `k` equals the target pubkey or whose recorded owner equals
|
||||||
completed, synchronously. Disconnect cancellation is terminal: a cancelled
|
the target pubkey, whether or not admission has completed, synchronously.
|
||||||
in-progress connection MUST NOT be subsequently admitted. Connections
|
Disconnect cancellation is terminal: a cancelled in-progress connection
|
||||||
|
MUST NOT be subsequently admitted. Connections
|
||||||
registered under another issuer are not affected.
|
registered under another issuer are not affected.
|
||||||
|
|
||||||
The deny set is held **in relay memory only** — no durable storage, no schema
|
The deny set is held **in relay memory only** — no durable storage, no schema
|
||||||
@@ -517,12 +552,14 @@ performs all pure authorization checks and then, as its single atomic admission
|
|||||||
mutation (step 7), simultaneously reserves the `(iss, jti)` replay identity and
|
mutation (step 7), simultaneously reserves the `(iss, jti)` replay identity and
|
||||||
inserts the deny entry — both or neither. A capacity failure at that step rejects
|
inserts the deny entry — both or neither. A capacity failure at that step rejects
|
||||||
`503`; neither the jti nor the deny entry is recorded, and the caller may safely
|
`503`; neither the jti nor the deny entry is recorded, and the caller may safely
|
||||||
retry the same signed command. On success, the relay closes all live connections
|
retry the same signed command. On success, the relay MUST close every live connection
|
||||||
registered under the command's issuer (`claims.iss`) whose proven `k` equals
|
registered under the command's issuer (`claims.iss`) whose proven key equals
|
||||||
|
`CommandResult.target_pubkey` or whose recorded owner equals
|
||||||
`CommandResult.target_pubkey`, whether or not admission has completed.
|
`CommandResult.target_pubkey`, whether or not admission has completed.
|
||||||
Disconnect cancellation is terminal: a cancelled in-progress connection MUST NOT
|
Disconnect cancellation is terminal: a cancelled in-progress connection MUST NOT
|
||||||
be subsequently admitted. This close scan matches the same
|
be subsequently admitted. This close scan matches the deny
|
||||||
`(iss, target_pubkey)` key as the deny entry; connections registered under
|
entry's `target_pubkey` against each connection's proven key and recorded
|
||||||
|
owner; connections registered under
|
||||||
another issuer are not affected. The `until` expiry is taken exclusively from
|
another issuer are not affected. The `until` expiry is taken exclusively from
|
||||||
the signed command JWT claim; the request body carries no `until` field. An
|
the signed command JWT claim; the request body carries no `until` field. An
|
||||||
unknown or unprovable pubkey is not an error; the relay responds `200`
|
unknown or unprovable pubkey is not an error; the relay responds `200`
|
||||||
@@ -743,8 +780,10 @@ On each protected HTTP request:
|
|||||||
`evidence_rejected`. [FI-TRACE-HTTP-INGRESS]
|
`evidence_rejected`. [FI-TRACE-HTTP-INGRESS]
|
||||||
5. Assert `verified.asserted_key == k`; mismatch → deny `authorization_denied`.
|
5. Assert `verified.asserted_key == k`; mismatch → deny `authorization_denied`.
|
||||||
[FI-TRACE-ASSERTION-KEY-MISMATCH]
|
[FI-TRACE-ASSERTION-KEY-MISMATCH]
|
||||||
6. Check deny set for `(iss, k)`; active entry (`now < until`) → deny
|
6. Check the deny set for `(iss, k)` and, if `verified.owner` is present, for
|
||||||
`authorization_denied`. [FI-TRACE-DENY-SET]
|
`(iss, verified.owner)`. Either active entry (`now < until`) denies
|
||||||
|
`authorization_denied`.
|
||||||
|
[FI-TRACE-DENY-SET]
|
||||||
7. Admit the request.
|
7. Admit the request.
|
||||||
|
|
||||||
A body is **authorization-relevant** whenever any body byte influences the
|
A body is **authorization-relevant** whenever any body byte influences the
|
||||||
@@ -809,14 +848,16 @@ normative behavior for them:
|
|||||||
- Per-session (`sid`-level) revocation: issuer-side. The relay's revocation
|
- Per-session (`sid`-level) revocation: issuer-side. The relay's revocation
|
||||||
primitive is pubkey-scoped and community-wide, keyed `(iss, target_pubkey)`
|
primitive is pubkey-scoped and community-wide, keyed `(iss, target_pubkey)`
|
||||||
(see Admin disconnect API). If an issuer revokes an entire identity, it
|
(see Admin disconnect API). If an issuer revokes an entire identity, it
|
||||||
enumerates and disconnects each key it has asserted for that identity.
|
disconnects each key it has asserted for that identity. A disconnect of an
|
||||||
|
owner key also covers that owner's agent keys at relays advertising
|
||||||
|
`agent_assertions: true`.
|
||||||
Individual session/device logout is handled by assertion expiry within the
|
Individual session/device logout is handled by assertion expiry within the
|
||||||
configured TTL window.
|
configured TTL window.
|
||||||
- Revocation signaling to the issuer/IdP: issuer-side; the issuer stops
|
- Revocation signaling to the issuer/IdP: issuer-side; the issuer stops
|
||||||
issuing assertions, which closes the relay window within assertion TTL.
|
issuing assertions, which closes the relay window within assertion TTL.
|
||||||
- Directory integration and account-offboarding automation: issuer-side.
|
- Directory integration and account-offboarding automation: issuer-side.
|
||||||
- Audit logging beyond what the relay operator chooses to retain: issuer-side.
|
- Audit logging beyond what the relay operator chooses to retain: issuer-side.
|
||||||
- Delegation: out of scope.
|
- Delegation other than "Agent keys": out of scope.
|
||||||
|
|
||||||
## Discovery
|
## Discovery
|
||||||
|
|
||||||
@@ -848,6 +889,11 @@ issuer stops issuance after a successful deny, the residual ceiling is
|
|||||||
issuer-operational property, not a protocol invariant this field can
|
issuer-operational property, not a protocol invariant this field can
|
||||||
advertise unconditionally.
|
advertise unconditionally.
|
||||||
|
|
||||||
|
`agent_assertions` (boolean, optional). `true` means the relay admits agent
|
||||||
|
assertions as defined in "Agent keys". Absent or `false` means it does not.
|
||||||
|
An issuer MUST NOT issue an agent assertion for a community whose relay does
|
||||||
|
not advertise `agent_assertions: true`.
|
||||||
|
|
||||||
Discovery MUST NOT state issuer URLs, audiences, claim names, tenant IDs, or
|
Discovery MUST NOT state issuer URLs, audiences, claim names, tenant IDs, or
|
||||||
deployment-local identifiers. [FI-TRACE-DISCOVERY-PRIVATE]
|
deployment-local identifiers. [FI-TRACE-DISCOVERY-PRIVATE]
|
||||||
|
|
||||||
@@ -896,7 +942,8 @@ For the deny-until-TTL disconnect model (issuer issues a successful disconnect
|
|||||||
call with an `until` timestamp), the relay inserts a deny entry for the target
|
call with an `until` timestamp), the relay inserts a deny entry for the target
|
||||||
pubkey with expiry `until` and then closes all matching connections synchronously.
|
pubkey with expiry `until` and then closes all matching connections synchronously.
|
||||||
Here, matching means connections registered under that issuer whose proven `k`
|
Here, matching means connections registered under that issuer whose proven `k`
|
||||||
equals that target pubkey, whether or not admission has completed; disconnect
|
equals that target pubkey or whose recorded owner equals the target pubkey,
|
||||||
|
whether or not admission has completed; disconnect
|
||||||
cancellation is terminal, so a cancelled in-progress connection MUST NOT be
|
cancellation is terminal, so a cancelled in-progress connection MUST NOT be
|
||||||
subsequently admitted. Connections registered under another issuer are not
|
subsequently admitted. Connections registered under another issuer are not
|
||||||
affected. Any subsequent admission attempt for that pubkey under the same issuer
|
affected. Any subsequent admission attempt for that pubkey under the same issuer
|
||||||
|
|||||||
Reference in New Issue
Block a user