regent-httpsig 0.5.1__py3-none-any.whl → 0.7.0__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.5.1
3
+ Version: 0.7.0
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,11 +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
- - An **expired** auth token gets the base protocol's plain challenge with a
178
- resource token carrying that token's **final** consumption record — the
179
- figure its issuer needs to settle the allocation (a record stated at or
180
- after `exp` is final; one on a live token is a snapshot and releases
181
- 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.
182
214
  - **Streaming** responses run in the draft's cost-omitted mode: `reserved` in
183
215
  the header, commit when the stream ends (set `request.state.budget_cost`
184
216
  mid-stream if you learn the actual), and the agent recovers the exact cost
@@ -190,11 +222,15 @@ production on [get4agent.com](https://get4agent.com).
190
222
  ```python
191
223
  from regent_httpsig import InMemoryMeter, ResponseSigner, make_usage_endpoint
192
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
+
193
229
  handler = make_usage_endpoint(
194
230
  meter,
195
- authenticate_ps=my_ps_authenticator, # verify the PS's jwks_uri signature
231
+ authenticate_ps=authenticate_ps,
196
232
  unit="USD", decimals=6,
197
- 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
198
234
  )
199
235
 
200
236
  @app.post("/usage")
@@ -238,9 +274,12 @@ from regent_httpsig import HttpsigConfig, HttpsigVerifier
238
274
 
239
275
  verifier = HttpsigVerifier(HttpsigConfig(
240
276
  trusted_agents=frozenset({"https://chatgpt.com", "https://operator.openai.com"}),
241
- max_age_hours=25, # reject signatures created earlier than this
242
- cache_ttl=600, # key-directory cache seconds
243
- ))
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
244
283
  ```
245
284
 
246
285
  Pass your app's shared client to reuse its pool: `HttpsigVerifier(http_client=my_async_client)`.
@@ -249,9 +288,13 @@ Pass your app's shared client to reuse its pool: `HttpsigVerifier(http_client=my
249
288
 
250
289
  - Web Bot Auth and AAuth are **IETF drafts** (RFC 9421 itself is a final standard). We track
251
290
  the drafts; breaking draft changes land as minor releases while we're 0.x.
252
- - **Ed25519 only** for now — it's what the agent ecosystem ships.
253
- - Body coverage (`content-digest`) is verified when covered by the signature, but this
254
- 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.
255
298
 
256
299
  ## Related projects
257
300
 
@@ -0,0 +1,16 @@
1
+ regent_httpsig/__init__.py,sha256=FPtO65WMG9iMMPqn30Ed6et3oBk1wetfhPUT6SE8hp4,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/sfv.py,sha256=JYaU-2_52-xlftt3MGy2Xzy3pMaa7LddZxPLQ9apjsk,10060
9
+ regent_httpsig/sign.py,sha256=myqUIhWe2y62DGhvyDzTPPlXKfta8EpHskpM0nJzbqk,13180
10
+ regent_httpsig/usage.py,sha256=6qeN7_4a-cNTJOglcgKumqNmag8w7-UF9xTJ1ohI5uU,14818
11
+ regent_httpsig/verify.py,sha256=fwsCWiKclez-KQzrIRwMNDdN4bpPbI45cxIU6_lqxCA,34783
12
+ regent_httpsig-0.7.0.dist-info/METADATA,sha256=hpYsbqEoM8loym73fK8m92C5j2QJKtaH1COKsZHNxrg,15979
13
+ regent_httpsig-0.7.0.dist-info/WHEEL,sha256=W3fkpkm7-wf9vBI5Z-7s0eWkeM-spu78I8Neb98DeEg,87
14
+ regent_httpsig-0.7.0.dist-info/entry_points.txt,sha256=SgZdmc27V14IAmwOjbBoCYLsbrALaqdTUHNHLbil2dU,59
15
+ regent_httpsig-0.7.0.dist-info/licenses/LICENSE,sha256=z8d0m5b2O9McPEK1xHG_dWgUBT6EfBDz6wA0F7xSPTA,11358
16
+ regent_httpsig-0.7.0.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=Ch-PdsMDo_uKG4U5A4xklU3jbCWdJTWXHG86n5VA7dc,1709
2
- regent_httpsig/budget.py,sha256=sjNJiFyfqLHEdnbh8e4bmqZr2gPveYynZ5eXUYpES_k,16967
3
- regent_httpsig/cli.py,sha256=WUSLQ2WuEX4E85L2C2EgoUfu5cWpj6MRSG1UVNHpzcw,2005
4
- regent_httpsig/config.py,sha256=_xAIFYAyNbXzKg-famXLAs7eNdYwuvNA7NHQxcno0G4,2439
5
- regent_httpsig/fastapi.py,sha256=RGkEHsNchEac7fXNYs2CT_MOETDktCxHcgQGwytSCCI,17714
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=PSIPXCtUZyh5cjn4SzwOgrXuJrwDIWu-kffJ-4NFeS0,9453
11
- regent_httpsig/verify.py,sha256=p9VtHZz9HWHIstDBxNvVrc_xJdubbRA_pN9DjQQDzpk,21716
12
- regent_httpsig-0.5.1.dist-info/METADATA,sha256=AWYCmTad0KRJFx6ODUZso9fyUcvN6CdU2fbxh0-LTXw,12496
13
- regent_httpsig-0.5.1.dist-info/WHEEL,sha256=zOwg4jB6zX2kU910N-cMawjivD6tO8NEWvE12je1bVk,87
14
- regent_httpsig-0.5.1.dist-info/entry_points.txt,sha256=SgZdmc27V14IAmwOjbBoCYLsbrALaqdTUHNHLbil2dU,59
15
- regent_httpsig-0.5.1.dist-info/licenses/LICENSE,sha256=z8d0m5b2O9McPEK1xHG_dWgUBT6EfBDz6wA0F7xSPTA,11358
16
- regent_httpsig-0.5.1.dist-info/RECORD,,