regent-httpsig 0.6.0__py3-none-any.whl → 0.7.1__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.5
2
2
  Name: regent-httpsig
3
- Version: 0.6.0
3
+ Version: 0.7.1
4
4
  Summary: Verify and sign AI agent HTTP traffic in Python — RFC 9421 HTTP Message Signatures: Web Bot Auth (what OpenAI ships) and AAuth.
5
5
  Project-URL: Homepage, https://github.com/regent-protocol/regent-httpsig
6
6
  Project-URL: Repository, https://github.com/regent-protocol/regent-httpsig
@@ -64,13 +64,20 @@ No FastAPI? The core has no framework dependencies:
64
64
 
65
65
  ```python
66
66
  verifier = HttpsigVerifier()
67
- sig = await verifier.verify(method, url, headers) # VerifiedSignature | None
67
+ sig = await verifier.verify(method, url, headers) # VerifiedSignature | None
68
+ out = await verifier.verify_detailed(method, url, headers, body)
69
+ # VerifiedSignature | VerificationError(code="expired_jwt", …) | None
68
70
  ```
69
71
 
70
72
  Verification is **enrichment by default**: no `Signature` header costs nothing, a bad
71
- signature yields `None`, and nothing ever raises on untrusted input. Use
73
+ signature yields `None`, and nothing ever raises on untrusted input. `verify_detailed()`
74
+ says *why* with the `Signature-Error` code of draft-hardt-httpbis-signature-key exactly
75
+ as AAuth -11 §11.3.4 assigns it (`invalid_signature`, `invalid_input`, `unsupported_scheme`,
76
+ `unsupported_algorithm`, `invalid_key`, `unknown_key`, `issuer_missing`, `issuer_mismatch`,
77
+ `invalid_jwt`, `expired_jwt`, `revoked_jwt`, `clock_skew`); `error.headers()` is the 401's
78
+ header set and `error.problem()` its problem+json body. Use
72
79
  `regent_httpsig.fastapi.RequiredSignatureDep` when a signature must be present — the 401
73
- tells the agent exactly how to sign.
80
+ carries that header, or `AAuth-Requirement: requirement=agent-token` when nothing was signed.
74
81
 
75
82
  > **Behind a reverse proxy?** The agent signed the *public* URL
76
83
  > (`https://api.example/…`), but your ASGI server sees `http://container/…`. The FastAPI
@@ -98,6 +105,18 @@ regent-httpsig keygen --agent https://myagent.example --out ./well-known/
98
105
  Publish the directory at `https://myagent.example/.well-known/http-message-signatures-directory`
99
106
  and every Web Bot Auth verifier on the internet can now identify your agent.
100
107
 
108
+ The same key signs as an **AAuth agent** (-11 §11.3.3) — the token in `Signature-Key`,
109
+ the required components covered, `Content-Digest` computed when there is a body:
110
+
111
+ ```python
112
+ headers = signer.sign_aauth("POST", url, {"content-type": "application/json"},
113
+ token=agent_token, body=raw_body)
114
+ ```
115
+
116
+ and a **server** (a PS or AS calling a resource's usage or revocation endpoint, §11.3.2)
117
+ signs under the `jwks_uri` scheme with `ServerSigner(seed=…, server_id="https://ps.example",
118
+ dwk="aauth-person.json")` — publish `.jwks()` at your metadata document's `jwks_uri`.
119
+
101
120
  ## What exactly is verified
102
121
 
103
122
  | Check | Status |
@@ -106,7 +125,9 @@ and every Web Bot Auth verifier on the internet can now identify your agent.
106
125
  | Web Bot Auth draft -05 A.2.2 — sf-dictionary `Signature-Agent` covered with `;key=` | ✅ in CI¹ |
107
126
  | Web Bot Auth A.2.3 — legacy sf-string form (**what OpenAI ships in production**) | ✅ in CI |
108
127
  | Sign → verify roundtrip (fresh keys, full pipeline) | ✅ in CI |
109
- | AAuth identity-mode roundtrip (`aa-agent+jwt` + `cnf.jwk` proof of possession) | ✅ in CI |
128
+ | AAuth -11 agent / person / auth tokens (`cnf.jwk` proof of possession, required components, body digest, 60 s `created` window, RS-51 token time, fully-specified algs, Ed25519 + ES256 possession keys) | ✅ in CI |
129
+ | AAuth -11 server scheme (`jwks_uri` with `id`/`dwk`/`kid`, `issuer_mismatch`, key rotation refetch) | ✅ in CI |
130
+ | Every -11 §11.3.4 Signature-Error code, `revoked_jwt` via the revocation record, RS-52 case-exact ids | ✅ in CI |
110
131
  | Signed by [`aauth-signing`](https://github.com/christian-posta/aauth-python-library) (jwt scheme, keyid-less) → verified | ✅² |
111
132
  | Tampered request / expired signature / wrong directory key rejected | ✅ in CI |
112
133
 
@@ -128,12 +149,16 @@ keyid-less shape is pinned in CI.
128
149
  `{Signature-Agent}/.well-known/http-message-signatures-directory`. Both wire forms of
129
150
  `Signature-Agent` are accepted — the current sf-dictionary and the legacy bare sf-string
130
151
  OpenAI actually sends.
131
- - **AAuth** (`draft-hardt-oauth-aauth-protocol`, identity-based mode): the agent carries a
132
- JWT `agent_token` in `Signature-Key`; the issuer's JWKS verifies the token, the token's
133
- `cnf.jwk` verifies the request signature. Install with `pip install 'regent-httpsig[aauth]'`.
134
- Tracks the **-11 editor's copy**: fully-specified algorithms (RFC 9864, `Ed25519` — with a
135
- transition flag for the -10 ecosystem's `EdDSA`) and **person tokens** (`aa-person+jwt`,
136
- opt-in via `HttpsigConfig.resource_url`).
152
+ - **AAuth** (`draft-hardt-oauth-aauth-protocol-11`): the agent carries its token in
153
+ `Signature-Key` under the `jwt` scheme — an agent token (`aa-agent+jwt`), a person token
154
+ (`aa-person+jwt`, opt-in via `HttpsigConfig.resource_url`) or an auth token
155
+ (`aa-auth+jwt`, pinned via `trusted_ps`); the issuer's JWKS, discovered through
156
+ `{iss}/.well-known/{dwk}`, verifies the token, the token's `cnf.jwk` verifies the request
157
+ signature. Servers sign under `jwks_uri` (`id`/`dwk`/`kid`). Install with
158
+ `pip install 'regent-httpsig[aauth]'`. 0.7.0 is the **-11 cut-over** — one dialect, as
159
+ published: fully-specified algorithms only (RFC 9864; `EdDSA` is refused), the required
160
+ covered components, the 60-second `created` window, no tolerance on `exp`, revoked tokens
161
+ named as revoked. `require_fully_specified_algs=False` survives for private test rigs only.
137
162
  For a full-protocol AAuth implementation (both roles, all token types) see
138
163
  [christian-posta/aauth-python-library](https://github.com/christian-posta/aauth-python-library) —
139
164
  this library is the thin relying-party verifier that handles both dialects.
@@ -174,14 +199,18 @@ production on [get4agent.com](https://get4agent.com).
174
199
 
175
200
  - `insufficient-budget` refusals carry **`required`** — the refused request's
176
201
  maximum cost — so the agent lowers its bound and retries instead of guessing.
177
- - **Revocation**: `make_revocation_endpoint(meter, authenticate_ps=…)` is the
178
- base protocol's endpoint; a revoked token stops spending at once, in-flight
179
- requests complete, and its final record rides on the next challenge.
180
- - An **expired** auth token gets the base protocol's plain challenge with a
181
- resource token carrying that token's **final** consumption record — the
182
- figure its issuer needs to settle the allocation (a record stated at or
183
- after `exp` is final; one on a live token is a snapshot and releases
184
- nothing).
202
+ - **Revocation** (-11 §11.12): `make_revocation_endpoint(meter, authenticate_ps=…)`
203
+ takes the signed `POST {"jti", "exp"}`, keys the record by the caller's
204
+ verified identity, answers an empty `200` always (no 404) and problem+json
205
+ `invalid_request` / `unsupported_iss`. A revoked token stops spending at
206
+ once, in-flight requests complete, and a later presentation is answered
207
+ `401` + `Signature-Error: error=revoked_jwt` + `AAuth-Requirement:
208
+ requirement=person-token` — no resource token (§11.12.5). Wire
209
+ `HttpsigVerifier(is_revoked=meter.is_revoked)` so the verifier names it first.
210
+ - An **expired** auth token is answered the same way with `expired_jwt`
211
+ (budgets editor's copy, 6 October): nothing is served or metered, and the
212
+ token's final consumption record reaches its issuer through the usage
213
+ endpoint, not on the challenge.
185
214
  - **Streaming** responses run in the draft's cost-omitted mode: `reserved` in
186
215
  the header, commit when the stream ends (set `request.state.budget_cost`
187
216
  mid-stream if you learn the actual), and the agent recovers the exact cost
@@ -193,11 +222,15 @@ production on [get4agent.com](https://get4agent.com).
193
222
  ```python
194
223
  from regent_httpsig import InMemoryMeter, ResponseSigner, make_usage_endpoint
195
224
 
225
+ async def authenticate_ps(request): # the PS signs as a server, §11.3.2
226
+ return await verifier.verify_server(request.method, str(request.url),
227
+ dict(request.headers), await request.body())
228
+
196
229
  handler = make_usage_endpoint(
197
230
  meter,
198
- authenticate_ps=my_ps_authenticator, # verify the PS's jwks_uri signature
231
+ authenticate_ps=authenticate_ps,
199
232
  unit="USD", decimals=6,
200
- signer=ResponseSigner(seed=SEED, jwks_url="https://api.example/jwks.json"),
233
+ signer=ResponseSigner(seed=SEED, server_id="https://api.example"), # dwk aauth-resource.json
201
234
  )
202
235
 
203
236
  @app.post("/usage")
@@ -241,9 +274,12 @@ from regent_httpsig import HttpsigConfig, HttpsigVerifier
241
274
 
242
275
  verifier = HttpsigVerifier(HttpsigConfig(
243
276
  trusted_agents=frozenset({"https://chatgpt.com", "https://operator.openai.com"}),
244
- max_age_hours=25, # reject signatures created earlier than this
245
- cache_ttl=600, # key-directory cache seconds
246
- ))
277
+ max_age_hours=25, # Web Bot Auth: reject signatures created earlier than this
278
+ signature_window_seconds=60, # AAuth -11: the `created` window (advertise as signature_window)
279
+ resource_url="https://api.example", # accept person / auth tokens for us
280
+ trusted_ps={"https://ps.example": ""}, # pinned issuers ("" = discover the JWKS)
281
+ cache_ttl=600, # key-directory cache seconds
282
+ ), is_revoked=meter.is_revoked) # optional: name revoked tokens as revoked
247
283
  ```
248
284
 
249
285
  Pass your app's shared client to reuse its pool: `HttpsigVerifier(http_client=my_async_client)`.
@@ -252,9 +288,13 @@ Pass your app's shared client to reuse its pool: `HttpsigVerifier(http_client=my
252
288
 
253
289
  - Web Bot Auth and AAuth are **IETF drafts** (RFC 9421 itself is a final standard). We track
254
290
  the drafts; breaking draft changes land as minor releases while we're 0.x.
255
- - **Ed25519 only** for now — it's what the agent ecosystem ships.
256
- - Body coverage (`content-digest`) is verified when covered by the signature, but this
257
- library does not require it; decide per-route whether you need it.
291
+ - Web Bot Auth is **Ed25519 only** — it's what the agent ecosystem ships. AAuth
292
+ possession keys may be Ed25519 or P-256 (ES256); RS256 is accepted for issuer keys.
293
+ - On the AAuth path a request with a body **must** cover `content-digest` and
294
+ `content-type` (-11 §11.3.3.1) and the digest is checked against the body you pass;
295
+ the FastAPI layer reads the body for you. Web Bot Auth keeps its per-route choice.
296
+ - Not in 0.7.0: issuing person/resource tokens, missions, the `202` deferred
297
+ `requirement=auth-token`, federated mode — the PS half of the protocol.
258
298
 
259
299
  ## Related projects
260
300
 
@@ -0,0 +1,17 @@
1
+ regent_httpsig/__init__.py,sha256=vroqNa6rQpwQFxhYpLEQcWhSltRiqvF0DZaPvn_lNCc,2191
2
+ regent_httpsig/budget.py,sha256=o4RMS8v9CfDXZlu3Ov_CWB798B-9mc5x4M_I3jj1MGA,21988
3
+ regent_httpsig/cli.py,sha256=WUSLQ2WuEX4E85L2C2EgoUfu5cWpj6MRSG1UVNHpzcw,2005
4
+ regent_httpsig/config.py,sha256=7fyIKZeUWs4BIJfc0TXJA_5P_v4-Z7iCFdEkmu_E9-E,3537
5
+ regent_httpsig/fastapi.py,sha256=gGUi08yMahtgRrOWN_339lm2Ano0Gu-zjRLAK81bXl0,21581
6
+ regent_httpsig/jwk.py,sha256=h4vgnIgpnyKeWdWpm7g_hEyXZOPVREWn-bD4JN07tT0,1637
7
+ regent_httpsig/netguard.py,sha256=Sqg08mdC94RWvnQLJaH4weZW0nN2VlTJF5wVF8VYGHY,2121
8
+ regent_httpsig/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
9
+ regent_httpsig/sfv.py,sha256=JYaU-2_52-xlftt3MGy2Xzy3pMaa7LddZxPLQ9apjsk,10060
10
+ regent_httpsig/sign.py,sha256=myqUIhWe2y62DGhvyDzTPPlXKfta8EpHskpM0nJzbqk,13180
11
+ regent_httpsig/usage.py,sha256=6qeN7_4a-cNTJOglcgKumqNmag8w7-UF9xTJ1ohI5uU,14818
12
+ regent_httpsig/verify.py,sha256=fwsCWiKclez-KQzrIRwMNDdN4bpPbI45cxIU6_lqxCA,34783
13
+ regent_httpsig-0.7.1.dist-info/METADATA,sha256=yP1tnPre-G3rIO0WBf07jsbUaPYaiLFUr0iBTxkSPpE,15979
14
+ regent_httpsig-0.7.1.dist-info/WHEEL,sha256=W3fkpkm7-wf9vBI5Z-7s0eWkeM-spu78I8Neb98DeEg,87
15
+ regent_httpsig-0.7.1.dist-info/entry_points.txt,sha256=SgZdmc27V14IAmwOjbBoCYLsbrALaqdTUHNHLbil2dU,59
16
+ regent_httpsig-0.7.1.dist-info/licenses/LICENSE,sha256=z8d0m5b2O9McPEK1xHG_dWgUBT6EfBDz6wA0F7xSPTA,11358
17
+ regent_httpsig-0.7.1.dist-info/RECORD,,
@@ -1,4 +1,4 @@
1
1
  Wheel-Version: 1.0
2
- Generator: hatchling 1.32.0
2
+ Generator: hatchling 1.32.4
3
3
  Root-Is-Purelib: true
4
4
  Tag: py3-none-any
@@ -1,16 +0,0 @@
1
- regent_httpsig/__init__.py,sha256=VDo7Yj7LvJ8newfCUpSFZbcncfLFAazMPbyw7MaQQsQ,1809
2
- regent_httpsig/budget.py,sha256=5yPRdai8ZTLp4lV-cn8y5kbsTtqE06XUhvT3JjJD_Ys,19667
3
- regent_httpsig/cli.py,sha256=WUSLQ2WuEX4E85L2C2EgoUfu5cWpj6MRSG1UVNHpzcw,2005
4
- regent_httpsig/config.py,sha256=_xAIFYAyNbXzKg-famXLAs7eNdYwuvNA7NHQxcno0G4,2439
5
- regent_httpsig/fastapi.py,sha256=-JKatY8RCVJm5el5zyPjnhwLbWYYje5iHQO9HpRmLqg,19220
6
- regent_httpsig/jwk.py,sha256=h4vgnIgpnyKeWdWpm7g_hEyXZOPVREWn-bD4JN07tT0,1637
7
- regent_httpsig/netguard.py,sha256=Sqg08mdC94RWvnQLJaH4weZW0nN2VlTJF5wVF8VYGHY,2121
8
- regent_httpsig/sfv.py,sha256=-nUUjyqyAMOSGro9Z0AIXiuchqsIAVaJWbKuIIO4fB0,7624
9
- regent_httpsig/sign.py,sha256=B5ChxFxuuKL0i10bGrgFImzyh8GMjWIqNDECXehBWTA,4397
10
- regent_httpsig/usage.py,sha256=094CwFHZVDIwmDf8WDg1p4nYlVCBkpi2DrZXdFbFzEo,11889
11
- regent_httpsig/verify.py,sha256=p9VtHZz9HWHIstDBxNvVrc_xJdubbRA_pN9DjQQDzpk,21716
12
- regent_httpsig-0.6.0.dist-info/METADATA,sha256=LBGeQCM6nfJ3f0hcQRTDjQKB2Our30hMhPMmwqCVZXQ,12725
13
- regent_httpsig-0.6.0.dist-info/WHEEL,sha256=zOwg4jB6zX2kU910N-cMawjivD6tO8NEWvE12je1bVk,87
14
- regent_httpsig-0.6.0.dist-info/entry_points.txt,sha256=SgZdmc27V14IAmwOjbBoCYLsbrALaqdTUHNHLbil2dU,59
15
- regent_httpsig-0.6.0.dist-info/licenses/LICENSE,sha256=z8d0m5b2O9McPEK1xHG_dWgUBT6EfBDz6wA0F7xSPTA,11358
16
- regent_httpsig-0.6.0.dist-info/RECORD,,