qnsi 0.3.0__tar.gz
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.
- qnsi-0.3.0/.gitignore +11 -0
- qnsi-0.3.0/LICENSE +17 -0
- qnsi-0.3.0/PKG-INFO +242 -0
- qnsi-0.3.0/README.md +200 -0
- qnsi-0.3.0/pyproject.toml +108 -0
- qnsi-0.3.0/src/qnsi/__init__.py +98 -0
- qnsi-0.3.0/src/qnsi/_activation.py +200 -0
- qnsi-0.3.0/src/qnsi/_client.py +120 -0
- qnsi-0.3.0/src/qnsi/_errors.py +56 -0
- qnsi-0.3.0/src/qnsi/_service.py +135 -0
- qnsi-0.3.0/src/qnsi/access.py +66 -0
- qnsi-0.3.0/src/qnsi/ai.py +160 -0
- qnsi-0.3.0/src/qnsi/audit.py +89 -0
- qnsi-0.3.0/src/qnsi/auth.py +82 -0
- qnsi-0.3.0/src/qnsi/billing.py +52 -0
- qnsi-0.3.0/src/qnsi/crypto/__init__.py +63 -0
- qnsi-0.3.0/src/qnsi/crypto/_registry.py +126 -0
- qnsi-0.3.0/src/qnsi/crypto/kem.py +216 -0
- qnsi-0.3.0/src/qnsi/crypto/sig.py +164 -0
- qnsi-0.3.0/src/qnsi/crypto_inventory.py +46 -0
- qnsi-0.3.0/src/qnsi/kms.py +180 -0
- qnsi-0.3.0/src/qnsi/py.typed +0 -0
- qnsi-0.3.0/src/qnsi/search.py +65 -0
- qnsi-0.3.0/src/qnsi/storage.py +66 -0
- qnsi-0.3.0/src/qnsi/tenant.py +81 -0
- qnsi-0.3.0/src/qnsi/vault.py +121 -0
- qnsi-0.3.0/src/qnsi/webhooks.py +160 -0
qnsi-0.3.0/.gitignore
ADDED
qnsi-0.3.0/LICENSE
ADDED
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
Apache License
|
|
2
|
+
Version 2.0, January 2004
|
|
3
|
+
http://www.apache.org/licenses/
|
|
4
|
+
|
|
5
|
+
Licensed under the Apache License, Version 2.0 (the "License");
|
|
6
|
+
you may not use this file except in compliance with the License.
|
|
7
|
+
You may obtain a copy of the License at
|
|
8
|
+
|
|
9
|
+
http://www.apache.org/licenses/LICENSE-2.0
|
|
10
|
+
|
|
11
|
+
Unless required by applicable law or agreed to in writing, software
|
|
12
|
+
distributed under the License is distributed on an "AS IS" BASIS,
|
|
13
|
+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
14
|
+
See the License for the specific language governing permissions and
|
|
15
|
+
limitations under the License.
|
|
16
|
+
|
|
17
|
+
Copyright 2026 HEOSSI (PTE.) LTD.
|
qnsi-0.3.0/PKG-INFO
ADDED
|
@@ -0,0 +1,242 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: qnsi
|
|
3
|
+
Version: 0.3.0
|
|
4
|
+
Summary: Official Python SDK for the QNSI Quantum-Native Security Infrastructure — post-quantum cryptography (ML-KEM, ML-DSA, SLH-DSA, Falcon via liboqs), PQC-encrypted vault, KMS, and immutable audit trails. Mirrors the @heossi/qnsi-* TypeScript SDKs.
|
|
5
|
+
Project-URL: Homepage, https://cloud.qnsi.heossi.com
|
|
6
|
+
Project-URL: Documentation, https://docs.qnsi.heossi.com/sdk/python
|
|
7
|
+
Project-URL: Repository, https://github.com/heossi-hq/qnsi-public
|
|
8
|
+
Project-URL: Issues, https://github.com/heossi-hq/qnsi-public/issues
|
|
9
|
+
Project-URL: Changelog, https://github.com/heossi-hq/qnsi-public/blob/main/sdks/python/qnsi/CHANGELOG.md
|
|
10
|
+
Author: HEOSSI (PTE.) LTD.
|
|
11
|
+
License: Apache-2.0
|
|
12
|
+
License-File: LICENSE
|
|
13
|
+
Keywords: audit,compliance,falcon,fips-203,fips-204,fips-205,kms,liboqs,ml-dsa,ml-kem,post-quantum,post-quantum-cryptography,pqc,qnsi,quantum-safe,slh-dsa,vault,zero-trust
|
|
14
|
+
Classifier: Development Status :: 4 - Beta
|
|
15
|
+
Classifier: Intended Audience :: Developers
|
|
16
|
+
Classifier: Intended Audience :: Information Technology
|
|
17
|
+
Classifier: License :: OSI Approved :: Apache Software License
|
|
18
|
+
Classifier: Operating System :: OS Independent
|
|
19
|
+
Classifier: Programming Language :: Python :: 3
|
|
20
|
+
Classifier: Programming Language :: Python :: 3 :: Only
|
|
21
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
22
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
23
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
24
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
25
|
+
Classifier: Topic :: Security :: Cryptography
|
|
26
|
+
Classifier: Topic :: Software Development :: Libraries :: Python Modules
|
|
27
|
+
Classifier: Typing :: Typed
|
|
28
|
+
Requires-Python: >=3.10
|
|
29
|
+
Requires-Dist: httpx<1.0,>=0.27
|
|
30
|
+
Requires-Dist: pyjwt<3.0,>=2.8
|
|
31
|
+
Provides-Extra: all
|
|
32
|
+
Requires-Dist: liboqs-python==0.12.0; extra == 'all'
|
|
33
|
+
Provides-Extra: crypto
|
|
34
|
+
Requires-Dist: liboqs-python==0.12.0; extra == 'crypto'
|
|
35
|
+
Provides-Extra: dev
|
|
36
|
+
Requires-Dist: mypy>=1.10; extra == 'dev'
|
|
37
|
+
Requires-Dist: pytest-asyncio>=0.23; extra == 'dev'
|
|
38
|
+
Requires-Dist: pytest-httpx>=0.30; extra == 'dev'
|
|
39
|
+
Requires-Dist: pytest>=8.0; extra == 'dev'
|
|
40
|
+
Requires-Dist: ruff>=0.6; extra == 'dev'
|
|
41
|
+
Description-Content-Type: text/markdown
|
|
42
|
+
|
|
43
|
+
# qnsp — Python SDK for the Quantum-Native Security Infrastructure
|
|
44
|
+
|
|
45
|
+
[](https://pypi.org/project/qnsp/)
|
|
46
|
+
[](https://pypi.org/project/qnsp/)
|
|
47
|
+
[](./LICENSE)
|
|
48
|
+
|
|
49
|
+
Typed Python client for QNSI — post-quantum cryptography (ML-KEM, ML-DSA, SLH-DSA, Falcon via liboqs), PQC-encrypted vault, server-side KMS, immutable audit trails. Same wire contracts as the official `@heossi/qnsi-*` TypeScript SDKs — pick whichever language fits your stack and the byte-for-byte outputs round-trip.
|
|
50
|
+
|
|
51
|
+
> **Free tier available.** Free-forever account at <https://cloud.qnsi.heossi.com/auth> — 60-second signup, no credit card. Includes 10 GB PQC storage, 50 000 API calls/month, 20 KMS keys, 25 vault secrets.
|
|
52
|
+
|
|
53
|
+
## Installation
|
|
54
|
+
|
|
55
|
+
Base install (HTTP clients for vault, KMS, audit):
|
|
56
|
+
|
|
57
|
+
```bash
|
|
58
|
+
pip install qnsp
|
|
59
|
+
```
|
|
60
|
+
|
|
61
|
+
With local PQC primitives (`qnsp.crypto` — wraps `liboqs-python` 0.12.0):
|
|
62
|
+
|
|
63
|
+
```bash
|
|
64
|
+
pip install 'qnsi[crypto]'
|
|
65
|
+
```
|
|
66
|
+
|
|
67
|
+
`liboqs-python` requires the `liboqs` C library available on the host. Easiest paths:
|
|
68
|
+
|
|
69
|
+
| Platform | Command |
|
|
70
|
+
| --- | --- |
|
|
71
|
+
| macOS | `brew install liboqs` |
|
|
72
|
+
| Debian/Ubuntu | `apt install liboqs-dev` |
|
|
73
|
+
| From source | `cmake -DBUILD_SHARED_LIBS=ON ...` — see <https://github.com/open-quantum-safe/liboqs> |
|
|
74
|
+
|
|
75
|
+
(A v0.3.x release will ship `cibuildwheel`-built wheels that bundle a self-contained liboqs binary, removing the system prerequisite.)
|
|
76
|
+
|
|
77
|
+
Requires Python 3.10+ and `httpx`. Tested on CPython 3.10, 3.11, 3.12, 3.13.
|
|
78
|
+
|
|
79
|
+
## Quick start
|
|
80
|
+
|
|
81
|
+
```python
|
|
82
|
+
import os
|
|
83
|
+
import base64
|
|
84
|
+
|
|
85
|
+
from qnsi import QnspClient
|
|
86
|
+
|
|
87
|
+
with QnspClient(api_key=os.environ["QNSP_API_KEY"]) as qnsp:
|
|
88
|
+
# ── Vault — PQC-encrypted secret storage ─────────────────────────
|
|
89
|
+
secret = qnsp.vault.create_secret(
|
|
90
|
+
name="openai-api-key",
|
|
91
|
+
payload_b64=base64.b64encode(b"sk-...").decode(),
|
|
92
|
+
algorithm="ml-kem-768",
|
|
93
|
+
)
|
|
94
|
+
fresh = qnsp.vault.get_secret(secret["id"])
|
|
95
|
+
|
|
96
|
+
# ── KMS — server-side PQC keys ──────────────────────────────────
|
|
97
|
+
key = qnsp.kms.create_key(algorithm="ml-dsa-65", purpose="signing")
|
|
98
|
+
signature = qnsp.kms.sign(key["keyId"], data=b"hello")
|
|
99
|
+
assert qnsp.kms.verify(key["keyId"], data=b"hello", signature=signature)
|
|
100
|
+
|
|
101
|
+
# ── Audit — immutable, hash-chained event log ───────────────────
|
|
102
|
+
qnsp.audit.log_event(
|
|
103
|
+
event_type="model.inference",
|
|
104
|
+
payload={"modelId": "gpt-4o", "latencyMs": 412},
|
|
105
|
+
)
|
|
106
|
+
|
|
107
|
+
# ── New in 0.3.0 — full parity with Go and Rust SDKs ────────────
|
|
108
|
+
qnsp.tenant.get_tenant(qnsp.tenant_id)
|
|
109
|
+
qnsp.access.check_permission(subject_id="user-1", permission="vault.read")
|
|
110
|
+
qnsp.billing.get_entitlements()
|
|
111
|
+
qnsp.crypto_inventory.get_readiness_score(qnsp.tenant_id)
|
|
112
|
+
qnsp.storage.put_object("uploads", "report.pdf", data=b"...")
|
|
113
|
+
qnsp.search.query("docs", vector=[0.1] * 768, top_k=5)
|
|
114
|
+
qnsp.ai.invoke_inference(model_id="gpt-4o", input={"prompt": "..."})
|
|
115
|
+
qnsp.auth.login(email="user@example.com", password="...", tenant_id=qnsp.tenant_id)
|
|
116
|
+
```
|
|
117
|
+
|
|
118
|
+
## Local PQC primitives
|
|
119
|
+
|
|
120
|
+
`qnsp.crypto` wraps `liboqs-python` so you don't have to write `oqs` calls yourself, and so the algorithm-name surface matches the rest of the QNSI ecosystem (TypeScript, Go, Rust):
|
|
121
|
+
|
|
122
|
+
```python
|
|
123
|
+
from qnsi.crypto import MlKem, MlDsa, SlhDsa, Falcon
|
|
124
|
+
|
|
125
|
+
# Module-Lattice KEM (FIPS 203)
|
|
126
|
+
kem = MlKem("ML-KEM-768")
|
|
127
|
+
pk, sk = kem.keygen()
|
|
128
|
+
enc = kem.encapsulate(pk)
|
|
129
|
+
recovered = kem.decapsulate(enc.ciphertext, sk)
|
|
130
|
+
assert recovered == enc.shared_secret
|
|
131
|
+
|
|
132
|
+
# Module-Lattice signatures (FIPS 204)
|
|
133
|
+
sig = MlDsa("ML-DSA-65")
|
|
134
|
+
sig_pk, sig_sk = sig.keygen()
|
|
135
|
+
signature = sig.sign(b"hello", sig_sk)
|
|
136
|
+
assert sig.verify(b"hello", signature, sig_pk)
|
|
137
|
+
|
|
138
|
+
# Stateless hash-based signatures (FIPS 205) — conservative, no lattice assumption
|
|
139
|
+
slh = SlhDsa("SLH-DSA-SHA2-128f")
|
|
140
|
+
|
|
141
|
+
# Compact lattice signatures (NIST PQC selection)
|
|
142
|
+
fal = Falcon("Falcon-512")
|
|
143
|
+
```
|
|
144
|
+
|
|
145
|
+
Sizes match the FIPS specs exactly (the SDK reads them from the linked liboqs build, so no inline literals drift).
|
|
146
|
+
|
|
147
|
+
## Verifying inbound webhooks
|
|
148
|
+
|
|
149
|
+
QNSI signs every webhook with HMAC-SHA-256. Verify the **raw body** before parsing JSON:
|
|
150
|
+
|
|
151
|
+
```python
|
|
152
|
+
from fastapi import FastAPI, Request, HTTPException
|
|
153
|
+
from qnsi import parse_qnsp_webhook, QnspWebhookError
|
|
154
|
+
|
|
155
|
+
app = FastAPI()
|
|
156
|
+
|
|
157
|
+
@app.post("/webhooks/qnsp")
|
|
158
|
+
async def receive(request: Request) -> dict:
|
|
159
|
+
body = await request.body()
|
|
160
|
+
try:
|
|
161
|
+
event = parse_qnsp_webhook(
|
|
162
|
+
body=body,
|
|
163
|
+
signature_header=request.headers.get("x-qnsp-signature", ""),
|
|
164
|
+
timestamp_header=request.headers.get("x-qnsp-timestamp"),
|
|
165
|
+
secret=os.environ["QNSP_WEBHOOK_SECRET"],
|
|
166
|
+
)
|
|
167
|
+
except QnspWebhookError as exc:
|
|
168
|
+
raise HTTPException(400, str(exc))
|
|
169
|
+
|
|
170
|
+
if event.event_type == "key.rotated":
|
|
171
|
+
...
|
|
172
|
+
return {"ok": True}
|
|
173
|
+
```
|
|
174
|
+
|
|
175
|
+
The verifier runs HMAC comparison in constant time, rejects timestamps older than 5 minutes by default (replay protection), and refuses payloads missing required fields.
|
|
176
|
+
|
|
177
|
+
## Error handling
|
|
178
|
+
|
|
179
|
+
All errors descend from `qnsp.QnspError`:
|
|
180
|
+
|
|
181
|
+
| Class | When |
|
|
182
|
+
| --- | --- |
|
|
183
|
+
| `QnspNetworkError` | DNS, TLS, timeout, or connection failure |
|
|
184
|
+
| `QnspAuthError` | API key rejected at activation |
|
|
185
|
+
| `QnspApiError` | A service returned 4xx/5xx with a structured body |
|
|
186
|
+
| `QnspWebhookError` | HMAC mismatch, expired timestamp, malformed body, etc. |
|
|
187
|
+
|
|
188
|
+
```python
|
|
189
|
+
from qnsi import QnspApiError, QnspNetworkError
|
|
190
|
+
|
|
191
|
+
try:
|
|
192
|
+
qnsp.vault.get_secret("missing")
|
|
193
|
+
except QnspApiError as exc:
|
|
194
|
+
print("HTTP", exc.status_code, exc.code, exc.body)
|
|
195
|
+
except QnspNetworkError as exc:
|
|
196
|
+
print("Could not reach QNSI:", exc)
|
|
197
|
+
```
|
|
198
|
+
|
|
199
|
+
## Activation + tier introspection
|
|
200
|
+
|
|
201
|
+
`QnspClient` performs a one-shot handshake against `/billing/v1/sdk/activate` on first use. The result is cached in memory; subsequent calls reuse it until ~1 minute before expiry. You can inspect the current activation:
|
|
202
|
+
|
|
203
|
+
```python
|
|
204
|
+
qnsp.tenant_id # resolved tenant
|
|
205
|
+
qnsp.tier # plan tier
|
|
206
|
+
qnsp.limits # full limits dict
|
|
207
|
+
qnsp.has_feature("sseEnabled") # convenience boolean
|
|
208
|
+
```
|
|
209
|
+
|
|
210
|
+
If the activation token is rotated server-side, the SDK invalidates its cache and retries the originating request once on a 401.
|
|
211
|
+
|
|
212
|
+
## What's covered today (v0.3.0 — full parity with Go and Rust SDKs)
|
|
213
|
+
|
|
214
|
+
Customer-facing service modules — every QNSI service callable through the edge gateway:
|
|
215
|
+
|
|
216
|
+
- `qnsp.vault` — secrets management (create / get / get-version / rotate / delete / list-versions)
|
|
217
|
+
- `qnsp.kms` — server-side PQC keys (create / list / get / rotate / delete / sign / verify / wrap / unwrap)
|
|
218
|
+
- `qnsp.audit` — immutable hash-chained event log (log-event / ingest-events / list-events)
|
|
219
|
+
- `qnsp.auth` — login, refresh, revoke, WebAuthn passkeys, MFA, SAML/OIDC federation, risk-based auth
|
|
220
|
+
- `qnsp.tenant` — tenant CRUD, crypto-policy management, current-health, current-quotas
|
|
221
|
+
- `qnsp.access` — RBAC roles, role assignments, `check_permission`
|
|
222
|
+
- `qnsp.billing` — entitlements, usage meters (single + batch), invoice listing, credit balance
|
|
223
|
+
- `qnsp.crypto_inventory` — Cryptographic Bill of Materials: assets, discovery runs, PQC readiness
|
|
224
|
+
- `qnsp.storage` — PQC-encrypted object storage with SSE-X
|
|
225
|
+
- `qnsp.search` — encrypted vector search (index lifecycle, `upsert_vectors`, `query`)
|
|
226
|
+
- `qnsp.ai` — model registry, AI workloads with enclave attestation, `invoke_inference`, artifacts
|
|
227
|
+
|
|
228
|
+
Local primitives + integration:
|
|
229
|
+
|
|
230
|
+
- `qnsp.crypto` (requires `qnsi[crypto]`) — ML-KEM (512/768/1024), ML-DSA (44/65/87), SLH-DSA (8 variants), Falcon (512/1024), plus BIKE, FrodoKEM, Classic-McEliece, MAYO, CROSS — every FIPS 203/204/205 finalist exposed by liboqs 0.12.0
|
|
231
|
+
- `qnsp.parse_qnsp_webhook` / `qnsp.verify_qnsp_webhook_signature` — HMAC-SHA-256 verify + replay protection
|
|
232
|
+
- `qnsp.QnspClient` — API-key activation with caching and 401 retry
|
|
233
|
+
|
|
234
|
+
## What's coming
|
|
235
|
+
|
|
236
|
+
- `AsyncQnspClient` — native-async variants using `httpx.AsyncClient`
|
|
237
|
+
- A `pytest` plugin that mocks the QNSI API for tests in your codebase
|
|
238
|
+
- Generated typed responses (currently `dict[str, Any]`) for every method
|
|
239
|
+
|
|
240
|
+
## License
|
|
241
|
+
|
|
242
|
+
Apache-2.0. See [LICENSE](./LICENSE).
|
qnsi-0.3.0/README.md
ADDED
|
@@ -0,0 +1,200 @@
|
|
|
1
|
+
# qnsp — Python SDK for the Quantum-Native Security Infrastructure
|
|
2
|
+
|
|
3
|
+
[](https://pypi.org/project/qnsp/)
|
|
4
|
+
[](https://pypi.org/project/qnsp/)
|
|
5
|
+
[](./LICENSE)
|
|
6
|
+
|
|
7
|
+
Typed Python client for QNSI — post-quantum cryptography (ML-KEM, ML-DSA, SLH-DSA, Falcon via liboqs), PQC-encrypted vault, server-side KMS, immutable audit trails. Same wire contracts as the official `@heossi/qnsi-*` TypeScript SDKs — pick whichever language fits your stack and the byte-for-byte outputs round-trip.
|
|
8
|
+
|
|
9
|
+
> **Free tier available.** Free-forever account at <https://cloud.qnsi.heossi.com/auth> — 60-second signup, no credit card. Includes 10 GB PQC storage, 50 000 API calls/month, 20 KMS keys, 25 vault secrets.
|
|
10
|
+
|
|
11
|
+
## Installation
|
|
12
|
+
|
|
13
|
+
Base install (HTTP clients for vault, KMS, audit):
|
|
14
|
+
|
|
15
|
+
```bash
|
|
16
|
+
pip install qnsp
|
|
17
|
+
```
|
|
18
|
+
|
|
19
|
+
With local PQC primitives (`qnsp.crypto` — wraps `liboqs-python` 0.12.0):
|
|
20
|
+
|
|
21
|
+
```bash
|
|
22
|
+
pip install 'qnsi[crypto]'
|
|
23
|
+
```
|
|
24
|
+
|
|
25
|
+
`liboqs-python` requires the `liboqs` C library available on the host. Easiest paths:
|
|
26
|
+
|
|
27
|
+
| Platform | Command |
|
|
28
|
+
| --- | --- |
|
|
29
|
+
| macOS | `brew install liboqs` |
|
|
30
|
+
| Debian/Ubuntu | `apt install liboqs-dev` |
|
|
31
|
+
| From source | `cmake -DBUILD_SHARED_LIBS=ON ...` — see <https://github.com/open-quantum-safe/liboqs> |
|
|
32
|
+
|
|
33
|
+
(A v0.3.x release will ship `cibuildwheel`-built wheels that bundle a self-contained liboqs binary, removing the system prerequisite.)
|
|
34
|
+
|
|
35
|
+
Requires Python 3.10+ and `httpx`. Tested on CPython 3.10, 3.11, 3.12, 3.13.
|
|
36
|
+
|
|
37
|
+
## Quick start
|
|
38
|
+
|
|
39
|
+
```python
|
|
40
|
+
import os
|
|
41
|
+
import base64
|
|
42
|
+
|
|
43
|
+
from qnsi import QnspClient
|
|
44
|
+
|
|
45
|
+
with QnspClient(api_key=os.environ["QNSP_API_KEY"]) as qnsp:
|
|
46
|
+
# ── Vault — PQC-encrypted secret storage ─────────────────────────
|
|
47
|
+
secret = qnsp.vault.create_secret(
|
|
48
|
+
name="openai-api-key",
|
|
49
|
+
payload_b64=base64.b64encode(b"sk-...").decode(),
|
|
50
|
+
algorithm="ml-kem-768",
|
|
51
|
+
)
|
|
52
|
+
fresh = qnsp.vault.get_secret(secret["id"])
|
|
53
|
+
|
|
54
|
+
# ── KMS — server-side PQC keys ──────────────────────────────────
|
|
55
|
+
key = qnsp.kms.create_key(algorithm="ml-dsa-65", purpose="signing")
|
|
56
|
+
signature = qnsp.kms.sign(key["keyId"], data=b"hello")
|
|
57
|
+
assert qnsp.kms.verify(key["keyId"], data=b"hello", signature=signature)
|
|
58
|
+
|
|
59
|
+
# ── Audit — immutable, hash-chained event log ───────────────────
|
|
60
|
+
qnsp.audit.log_event(
|
|
61
|
+
event_type="model.inference",
|
|
62
|
+
payload={"modelId": "gpt-4o", "latencyMs": 412},
|
|
63
|
+
)
|
|
64
|
+
|
|
65
|
+
# ── New in 0.3.0 — full parity with Go and Rust SDKs ────────────
|
|
66
|
+
qnsp.tenant.get_tenant(qnsp.tenant_id)
|
|
67
|
+
qnsp.access.check_permission(subject_id="user-1", permission="vault.read")
|
|
68
|
+
qnsp.billing.get_entitlements()
|
|
69
|
+
qnsp.crypto_inventory.get_readiness_score(qnsp.tenant_id)
|
|
70
|
+
qnsp.storage.put_object("uploads", "report.pdf", data=b"...")
|
|
71
|
+
qnsp.search.query("docs", vector=[0.1] * 768, top_k=5)
|
|
72
|
+
qnsp.ai.invoke_inference(model_id="gpt-4o", input={"prompt": "..."})
|
|
73
|
+
qnsp.auth.login(email="user@example.com", password="...", tenant_id=qnsp.tenant_id)
|
|
74
|
+
```
|
|
75
|
+
|
|
76
|
+
## Local PQC primitives
|
|
77
|
+
|
|
78
|
+
`qnsp.crypto` wraps `liboqs-python` so you don't have to write `oqs` calls yourself, and so the algorithm-name surface matches the rest of the QNSI ecosystem (TypeScript, Go, Rust):
|
|
79
|
+
|
|
80
|
+
```python
|
|
81
|
+
from qnsi.crypto import MlKem, MlDsa, SlhDsa, Falcon
|
|
82
|
+
|
|
83
|
+
# Module-Lattice KEM (FIPS 203)
|
|
84
|
+
kem = MlKem("ML-KEM-768")
|
|
85
|
+
pk, sk = kem.keygen()
|
|
86
|
+
enc = kem.encapsulate(pk)
|
|
87
|
+
recovered = kem.decapsulate(enc.ciphertext, sk)
|
|
88
|
+
assert recovered == enc.shared_secret
|
|
89
|
+
|
|
90
|
+
# Module-Lattice signatures (FIPS 204)
|
|
91
|
+
sig = MlDsa("ML-DSA-65")
|
|
92
|
+
sig_pk, sig_sk = sig.keygen()
|
|
93
|
+
signature = sig.sign(b"hello", sig_sk)
|
|
94
|
+
assert sig.verify(b"hello", signature, sig_pk)
|
|
95
|
+
|
|
96
|
+
# Stateless hash-based signatures (FIPS 205) — conservative, no lattice assumption
|
|
97
|
+
slh = SlhDsa("SLH-DSA-SHA2-128f")
|
|
98
|
+
|
|
99
|
+
# Compact lattice signatures (NIST PQC selection)
|
|
100
|
+
fal = Falcon("Falcon-512")
|
|
101
|
+
```
|
|
102
|
+
|
|
103
|
+
Sizes match the FIPS specs exactly (the SDK reads them from the linked liboqs build, so no inline literals drift).
|
|
104
|
+
|
|
105
|
+
## Verifying inbound webhooks
|
|
106
|
+
|
|
107
|
+
QNSI signs every webhook with HMAC-SHA-256. Verify the **raw body** before parsing JSON:
|
|
108
|
+
|
|
109
|
+
```python
|
|
110
|
+
from fastapi import FastAPI, Request, HTTPException
|
|
111
|
+
from qnsi import parse_qnsp_webhook, QnspWebhookError
|
|
112
|
+
|
|
113
|
+
app = FastAPI()
|
|
114
|
+
|
|
115
|
+
@app.post("/webhooks/qnsp")
|
|
116
|
+
async def receive(request: Request) -> dict:
|
|
117
|
+
body = await request.body()
|
|
118
|
+
try:
|
|
119
|
+
event = parse_qnsp_webhook(
|
|
120
|
+
body=body,
|
|
121
|
+
signature_header=request.headers.get("x-qnsp-signature", ""),
|
|
122
|
+
timestamp_header=request.headers.get("x-qnsp-timestamp"),
|
|
123
|
+
secret=os.environ["QNSP_WEBHOOK_SECRET"],
|
|
124
|
+
)
|
|
125
|
+
except QnspWebhookError as exc:
|
|
126
|
+
raise HTTPException(400, str(exc))
|
|
127
|
+
|
|
128
|
+
if event.event_type == "key.rotated":
|
|
129
|
+
...
|
|
130
|
+
return {"ok": True}
|
|
131
|
+
```
|
|
132
|
+
|
|
133
|
+
The verifier runs HMAC comparison in constant time, rejects timestamps older than 5 minutes by default (replay protection), and refuses payloads missing required fields.
|
|
134
|
+
|
|
135
|
+
## Error handling
|
|
136
|
+
|
|
137
|
+
All errors descend from `qnsp.QnspError`:
|
|
138
|
+
|
|
139
|
+
| Class | When |
|
|
140
|
+
| --- | --- |
|
|
141
|
+
| `QnspNetworkError` | DNS, TLS, timeout, or connection failure |
|
|
142
|
+
| `QnspAuthError` | API key rejected at activation |
|
|
143
|
+
| `QnspApiError` | A service returned 4xx/5xx with a structured body |
|
|
144
|
+
| `QnspWebhookError` | HMAC mismatch, expired timestamp, malformed body, etc. |
|
|
145
|
+
|
|
146
|
+
```python
|
|
147
|
+
from qnsi import QnspApiError, QnspNetworkError
|
|
148
|
+
|
|
149
|
+
try:
|
|
150
|
+
qnsp.vault.get_secret("missing")
|
|
151
|
+
except QnspApiError as exc:
|
|
152
|
+
print("HTTP", exc.status_code, exc.code, exc.body)
|
|
153
|
+
except QnspNetworkError as exc:
|
|
154
|
+
print("Could not reach QNSI:", exc)
|
|
155
|
+
```
|
|
156
|
+
|
|
157
|
+
## Activation + tier introspection
|
|
158
|
+
|
|
159
|
+
`QnspClient` performs a one-shot handshake against `/billing/v1/sdk/activate` on first use. The result is cached in memory; subsequent calls reuse it until ~1 minute before expiry. You can inspect the current activation:
|
|
160
|
+
|
|
161
|
+
```python
|
|
162
|
+
qnsp.tenant_id # resolved tenant
|
|
163
|
+
qnsp.tier # plan tier
|
|
164
|
+
qnsp.limits # full limits dict
|
|
165
|
+
qnsp.has_feature("sseEnabled") # convenience boolean
|
|
166
|
+
```
|
|
167
|
+
|
|
168
|
+
If the activation token is rotated server-side, the SDK invalidates its cache and retries the originating request once on a 401.
|
|
169
|
+
|
|
170
|
+
## What's covered today (v0.3.0 — full parity with Go and Rust SDKs)
|
|
171
|
+
|
|
172
|
+
Customer-facing service modules — every QNSI service callable through the edge gateway:
|
|
173
|
+
|
|
174
|
+
- `qnsp.vault` — secrets management (create / get / get-version / rotate / delete / list-versions)
|
|
175
|
+
- `qnsp.kms` — server-side PQC keys (create / list / get / rotate / delete / sign / verify / wrap / unwrap)
|
|
176
|
+
- `qnsp.audit` — immutable hash-chained event log (log-event / ingest-events / list-events)
|
|
177
|
+
- `qnsp.auth` — login, refresh, revoke, WebAuthn passkeys, MFA, SAML/OIDC federation, risk-based auth
|
|
178
|
+
- `qnsp.tenant` — tenant CRUD, crypto-policy management, current-health, current-quotas
|
|
179
|
+
- `qnsp.access` — RBAC roles, role assignments, `check_permission`
|
|
180
|
+
- `qnsp.billing` — entitlements, usage meters (single + batch), invoice listing, credit balance
|
|
181
|
+
- `qnsp.crypto_inventory` — Cryptographic Bill of Materials: assets, discovery runs, PQC readiness
|
|
182
|
+
- `qnsp.storage` — PQC-encrypted object storage with SSE-X
|
|
183
|
+
- `qnsp.search` — encrypted vector search (index lifecycle, `upsert_vectors`, `query`)
|
|
184
|
+
- `qnsp.ai` — model registry, AI workloads with enclave attestation, `invoke_inference`, artifacts
|
|
185
|
+
|
|
186
|
+
Local primitives + integration:
|
|
187
|
+
|
|
188
|
+
- `qnsp.crypto` (requires `qnsi[crypto]`) — ML-KEM (512/768/1024), ML-DSA (44/65/87), SLH-DSA (8 variants), Falcon (512/1024), plus BIKE, FrodoKEM, Classic-McEliece, MAYO, CROSS — every FIPS 203/204/205 finalist exposed by liboqs 0.12.0
|
|
189
|
+
- `qnsp.parse_qnsp_webhook` / `qnsp.verify_qnsp_webhook_signature` — HMAC-SHA-256 verify + replay protection
|
|
190
|
+
- `qnsp.QnspClient` — API-key activation with caching and 401 retry
|
|
191
|
+
|
|
192
|
+
## What's coming
|
|
193
|
+
|
|
194
|
+
- `AsyncQnspClient` — native-async variants using `httpx.AsyncClient`
|
|
195
|
+
- A `pytest` plugin that mocks the QNSI API for tests in your codebase
|
|
196
|
+
- Generated typed responses (currently `dict[str, Any]`) for every method
|
|
197
|
+
|
|
198
|
+
## License
|
|
199
|
+
|
|
200
|
+
Apache-2.0. See [LICENSE](./LICENSE).
|
|
@@ -0,0 +1,108 @@
|
|
|
1
|
+
[build-system]
|
|
2
|
+
requires = ["hatchling>=1.27"]
|
|
3
|
+
build-backend = "hatchling.build"
|
|
4
|
+
|
|
5
|
+
[project]
|
|
6
|
+
name = "qnsi"
|
|
7
|
+
version = "0.3.0"
|
|
8
|
+
description = "Official Python SDK for the QNSI Quantum-Native Security Infrastructure — post-quantum cryptography (ML-KEM, ML-DSA, SLH-DSA, Falcon via liboqs), PQC-encrypted vault, KMS, and immutable audit trails. Mirrors the @heossi/qnsi-* TypeScript SDKs."
|
|
9
|
+
readme = "README.md"
|
|
10
|
+
license = { text = "Apache-2.0" }
|
|
11
|
+
requires-python = ">=3.10"
|
|
12
|
+
authors = [
|
|
13
|
+
{ name = "HEOSSI (PTE.) LTD." },
|
|
14
|
+
]
|
|
15
|
+
keywords = [
|
|
16
|
+
"qnsi",
|
|
17
|
+
"post-quantum",
|
|
18
|
+
"post-quantum-cryptography",
|
|
19
|
+
"pqc",
|
|
20
|
+
"vault",
|
|
21
|
+
"kms",
|
|
22
|
+
"audit",
|
|
23
|
+
"compliance",
|
|
24
|
+
"fips-203",
|
|
25
|
+
"fips-204",
|
|
26
|
+
"fips-205",
|
|
27
|
+
"ml-kem",
|
|
28
|
+
"ml-dsa",
|
|
29
|
+
"slh-dsa",
|
|
30
|
+
"falcon",
|
|
31
|
+
"liboqs",
|
|
32
|
+
"quantum-safe",
|
|
33
|
+
"zero-trust",
|
|
34
|
+
]
|
|
35
|
+
classifiers = [
|
|
36
|
+
"Development Status :: 4 - Beta",
|
|
37
|
+
"Intended Audience :: Developers",
|
|
38
|
+
"Intended Audience :: Information Technology",
|
|
39
|
+
"License :: OSI Approved :: Apache Software License",
|
|
40
|
+
"Operating System :: OS Independent",
|
|
41
|
+
"Programming Language :: Python :: 3",
|
|
42
|
+
"Programming Language :: Python :: 3 :: Only",
|
|
43
|
+
"Programming Language :: Python :: 3.10",
|
|
44
|
+
"Programming Language :: Python :: 3.11",
|
|
45
|
+
"Programming Language :: Python :: 3.12",
|
|
46
|
+
"Programming Language :: Python :: 3.13",
|
|
47
|
+
"Topic :: Security :: Cryptography",
|
|
48
|
+
"Topic :: Software Development :: Libraries :: Python Modules",
|
|
49
|
+
"Typing :: Typed",
|
|
50
|
+
]
|
|
51
|
+
dependencies = [
|
|
52
|
+
"httpx>=0.27,<1.0",
|
|
53
|
+
"PyJWT>=2.8,<3.0",
|
|
54
|
+
]
|
|
55
|
+
|
|
56
|
+
[project.optional-dependencies]
|
|
57
|
+
# Local PQC primitives — wraps liboqs via the open-quantum-safe Python binding.
|
|
58
|
+
# `liboqs-python` 0.12.0 expects the liboqs C library to be discoverable on the
|
|
59
|
+
# system. Inside the QNSI monorepo this is satisfied automatically by
|
|
60
|
+
# `tooling/liboqs-src/build/`. End users install via:
|
|
61
|
+
# - macOS: brew install liboqs && pip install 'qnsi[crypto]'
|
|
62
|
+
# - Debian: apt install liboqs-dev && pip install 'qnsi[crypto]'
|
|
63
|
+
# - Other: build liboqs from source (see https://github.com/open-quantum-safe/liboqs)
|
|
64
|
+
# A v0.3.x follow-up will ship cibuildwheel-built wheels that bundle a fully
|
|
65
|
+
# self-contained liboqs binary — same model as @heossi/liboqs-native's prebuilds/.
|
|
66
|
+
crypto = [
|
|
67
|
+
"liboqs-python==0.12.0",
|
|
68
|
+
]
|
|
69
|
+
dev = [
|
|
70
|
+
"pytest>=8.0",
|
|
71
|
+
"pytest-asyncio>=0.23",
|
|
72
|
+
"pytest-httpx>=0.30",
|
|
73
|
+
"mypy>=1.10",
|
|
74
|
+
"ruff>=0.6",
|
|
75
|
+
]
|
|
76
|
+
all = [
|
|
77
|
+
"qnsi[crypto]",
|
|
78
|
+
]
|
|
79
|
+
|
|
80
|
+
[project.urls]
|
|
81
|
+
Homepage = "https://cloud.qnsi.heossi.com"
|
|
82
|
+
Documentation = "https://docs.qnsi.heossi.com/sdk/python"
|
|
83
|
+
Repository = "https://github.com/heossi-hq/qnsi-public"
|
|
84
|
+
Issues = "https://github.com/heossi-hq/qnsi-public/issues"
|
|
85
|
+
Changelog = "https://github.com/heossi-hq/qnsi-public/blob/main/sdks/python/qnsi/CHANGELOG.md"
|
|
86
|
+
|
|
87
|
+
[tool.hatch.build.targets.wheel]
|
|
88
|
+
packages = ["src/qnsi"]
|
|
89
|
+
|
|
90
|
+
[tool.hatch.build.targets.sdist]
|
|
91
|
+
include = ["src", "README.md", "LICENSE", "pyproject.toml"]
|
|
92
|
+
|
|
93
|
+
[tool.ruff]
|
|
94
|
+
line-length = 100
|
|
95
|
+
target-version = "py310"
|
|
96
|
+
|
|
97
|
+
[tool.ruff.lint]
|
|
98
|
+
select = ["E", "F", "W", "I", "N", "UP", "B", "C4", "SIM", "RUF"]
|
|
99
|
+
|
|
100
|
+
[tool.mypy]
|
|
101
|
+
python_version = "3.10"
|
|
102
|
+
strict = true
|
|
103
|
+
warn_return_any = true
|
|
104
|
+
warn_unused_configs = true
|
|
105
|
+
|
|
106
|
+
[tool.pytest.ini_options]
|
|
107
|
+
testpaths = ["tests"]
|
|
108
|
+
asyncio_mode = "auto"
|
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
"""QNSI — official Python SDK for the Quantum-Native Security Infrastructure.
|
|
2
|
+
|
|
3
|
+
Mirrors the surface of the ``@heossi/qnsi-*`` TypeScript SDK family for the Python
|
|
4
|
+
ecosystem. Same wire contracts, same algorithm names, same FIPS 203/204/205
|
|
5
|
+
posture — pick whichever language fits your stack and the byte-for-byte
|
|
6
|
+
outputs round-trip.
|
|
7
|
+
|
|
8
|
+
Quick start::
|
|
9
|
+
|
|
10
|
+
from qnsi import QnspClient
|
|
11
|
+
|
|
12
|
+
qnsp = QnspClient(api_key=os.environ["QNSP_API_KEY"])
|
|
13
|
+
|
|
14
|
+
# Vault
|
|
15
|
+
secret = qnsp.vault.create_secret(
|
|
16
|
+
name="openai-key",
|
|
17
|
+
payload_b64=base64.b64encode(b"sk-...").decode(),
|
|
18
|
+
)
|
|
19
|
+
|
|
20
|
+
# KMS
|
|
21
|
+
key = qnsp.kms.create_key(algorithm="ml-dsa-65", purpose="signing")
|
|
22
|
+
sig = qnsp.kms.sign(key["keyId"], data=b"hello")
|
|
23
|
+
|
|
24
|
+
# Audit
|
|
25
|
+
qnsp.audit.log_event(event_type="model.inference", payload={"modelId": "gpt-4o"})
|
|
26
|
+
|
|
27
|
+
Local PQC primitives (requires ``qnsi[crypto]``)::
|
|
28
|
+
|
|
29
|
+
from qnsi.crypto import MlKem, MlDsa
|
|
30
|
+
|
|
31
|
+
kem = MlKem("ML-KEM-768")
|
|
32
|
+
pk, sk = kem.keygen()
|
|
33
|
+
enc = kem.encapsulate(pk)
|
|
34
|
+
assert kem.decapsulate(enc.ciphertext, sk) == enc.shared_secret
|
|
35
|
+
|
|
36
|
+
Webhook verification::
|
|
37
|
+
|
|
38
|
+
from qnsi import parse_qnsp_webhook, QnspWebhookError
|
|
39
|
+
|
|
40
|
+
event = parse_qnsp_webhook(
|
|
41
|
+
body=raw_body,
|
|
42
|
+
signature_header=request.headers["x-qnsp-signature"],
|
|
43
|
+
timestamp_header=request.headers["x-qnsp-timestamp"],
|
|
44
|
+
secret=os.environ["QNSP_WEBHOOK_SECRET"],
|
|
45
|
+
)
|
|
46
|
+
|
|
47
|
+
Free signup at https://cloud.qnsi.heossi.com/auth — no credit card.
|
|
48
|
+
"""
|
|
49
|
+
|
|
50
|
+
from qnsi._client import QnspClient
|
|
51
|
+
from qnsi._errors import (
|
|
52
|
+
QnspApiError,
|
|
53
|
+
QnspAuthError,
|
|
54
|
+
QnspError,
|
|
55
|
+
QnspNetworkError,
|
|
56
|
+
QnspWebhookError,
|
|
57
|
+
)
|
|
58
|
+
from qnsi.access import AccessClient
|
|
59
|
+
from qnsi.ai import AIClient
|
|
60
|
+
from qnsi.audit import AuditClient
|
|
61
|
+
from qnsi.auth import AuthClient
|
|
62
|
+
from qnsi.billing import BillingClient
|
|
63
|
+
from qnsi.crypto_inventory import CryptoInventoryClient
|
|
64
|
+
from qnsi.kms import KmsClient
|
|
65
|
+
from qnsi.search import SearchClient
|
|
66
|
+
from qnsi.storage import StorageClient
|
|
67
|
+
from qnsi.tenant import TenantClient
|
|
68
|
+
from qnsi.vault import VaultClient
|
|
69
|
+
from qnsi.webhooks import (
|
|
70
|
+
QnspWebhookEvent,
|
|
71
|
+
parse_qnsp_webhook,
|
|
72
|
+
verify_qnsp_webhook_signature,
|
|
73
|
+
)
|
|
74
|
+
|
|
75
|
+
__version__ = "0.3.0"
|
|
76
|
+
|
|
77
|
+
__all__ = [
|
|
78
|
+
"AccessClient",
|
|
79
|
+
"AIClient",
|
|
80
|
+
"AuditClient",
|
|
81
|
+
"AuthClient",
|
|
82
|
+
"BillingClient",
|
|
83
|
+
"CryptoInventoryClient",
|
|
84
|
+
"KmsClient",
|
|
85
|
+
"QnspApiError",
|
|
86
|
+
"QnspAuthError",
|
|
87
|
+
"QnspClient",
|
|
88
|
+
"QnspError",
|
|
89
|
+
"QnspNetworkError",
|
|
90
|
+
"QnspWebhookError",
|
|
91
|
+
"QnspWebhookEvent",
|
|
92
|
+
"SearchClient",
|
|
93
|
+
"StorageClient",
|
|
94
|
+
"TenantClient",
|
|
95
|
+
"VaultClient",
|
|
96
|
+
"parse_qnsp_webhook",
|
|
97
|
+
"verify_qnsp_webhook_signature",
|
|
98
|
+
]
|