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.
- regent_httpsig/__init__.py +33 -7
- regent_httpsig/budget.py +104 -4
- regent_httpsig/config.py +31 -16
- regent_httpsig/fastapi.py +189 -106
- regent_httpsig/sfv.py +58 -6
- regent_httpsig/sign.py +232 -24
- regent_httpsig/usage.py +119 -14
- regent_httpsig/verify.py +456 -202
- {regent_httpsig-0.5.1.dist-info → regent_httpsig-0.7.0.dist-info}/METADATA +67 -24
- regent_httpsig-0.7.0.dist-info/RECORD +16 -0
- {regent_httpsig-0.5.1.dist-info → regent_httpsig-0.7.0.dist-info}/WHEEL +1 -1
- regent_httpsig-0.5.1.dist-info/RECORD +0 -16
- {regent_httpsig-0.5.1.dist-info → regent_httpsig-0.7.0.dist-info}/entry_points.txt +0 -0
- {regent_httpsig-0.5.1.dist-info → regent_httpsig-0.7.0.dist-info}/licenses/LICENSE +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.5
|
|
2
2
|
Name: regent-httpsig
|
|
3
|
-
Version: 0.
|
|
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)
|
|
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.
|
|
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
|
-
|
|
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
|
|
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
|
|
132
|
-
|
|
133
|
-
`
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
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
|
-
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
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=
|
|
231
|
+
authenticate_ps=authenticate_ps,
|
|
196
232
|
unit="USD", decimals=6,
|
|
197
|
-
signer=ResponseSigner(seed=SEED,
|
|
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,
|
|
242
|
-
|
|
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**
|
|
253
|
-
|
|
254
|
-
|
|
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,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,,
|
|
File without changes
|
|
File without changes
|