README.md
7.2 KB · 233 lines · markdown Raw
1 # PQC MCP Transport
2
3 ![PQC Native](https://img.shields.io/badge/PQC-Native-blue)
4 ![ML-DSA-87](https://img.shields.io/badge/ML--DSA--87-FIPS%20204-green)
5 ![License](https://img.shields.io/badge/License-Apache%202.0-orange)
6
7 Post-quantum secured transport layer for the **Model Context Protocol (MCP)**. Every JSON-RPC message is signed with **ML-DSA** (FIPS 204) digital signatures, providing cryptographic authentication, integrity verification, and replay protection that is resistant to both classical and quantum attacks.
8
9 ## Installation
10
11 ```bash
12 pip install pqc-mcp-transport
13 ```
14
15 For development:
16
17 ```bash
18 pip install pqc-mcp-transport[dev]
19 ```
20
21 ## Quick Start
22
23 ### Server
24
25 ```python
26 import asyncio
27 from quantumshield.identity.agent import AgentIdentity
28 from pqc_mcp_transport import PQCMCPServer
29
30 server_identity = AgentIdentity.create("my-server")
31 server = PQCMCPServer(identity=server_identity)
32
33 @server.tool("greet", description="Greet someone")
34 async def greet(name: str) -> str:
35 return f"Hello, {name}!"
36
37 asyncio.run(server.run(port=8080))
38 ```
39
40 ### Client
41
42 ```python
43 import asyncio
44 from quantumshield.identity.agent import AgentIdentity
45 from pqc_mcp_transport import PQCMCPClient
46
47 async def main():
48 agent = AgentIdentity.create("my-client")
49 client = PQCMCPClient(identity=agent, server_url="http://localhost:8080")
50
51 session = await client.connect() # PQC handshake
52 result = await client.call_tool("greet", {"name": "World"})
53 print(result) # Verified response
54 await client.close()
55
56 asyncio.run(main())
57 ```
58
59 ## Architecture
60
61 ```
62 Client Server
63 ------ ------
64 | |
65 | 1. HandshakeRequest (signed with ML-DSA) |
66 |---------------------------------------------->|
67 | | verify client sig
68 | 2. HandshakeResponse (signed with ML-DSA) |
69 |<----------------------------------------------|
70 | verify server sig |
71 | |
72 | === Session Established (mutual auth) === |
73 | |
74 | 3. JSON-RPC Request + _pqc envelope |
75 |---------------------------------------------->|
76 | verify sig, | check nonce,
77 | execute tool | sign response
78 | 4. JSON-RPC Response + _pqc envelope |
79 |<----------------------------------------------|
80 | verify response sig |
81 ```
82
83 ## Protocol Specification
84
85 ### Handshake (Mutual Authentication)
86
87 1. **Client** generates a nonce, signs `{did}:{nonce}:{timestamp}` with its ML-DSA private key, and sends a `HandshakeRequest`.
88 2. **Server** verifies the client's signature, generates its own nonce and a session ID, signs `{did}:{client_nonce}:{server_nonce}:{session_id}`, and returns a `HandshakeResponse`.
89 3. **Client** verifies the server's signature and the echoed nonce. A `PQCSession` is created on both sides.
90
91 ### Message Format
92
93 Every MCP JSON-RPC message carries a `_pqc` envelope:
94
95 ```json
96 {
97 "jsonrpc": "2.0",
98 "method": "tools/call",
99 "id": "abc123",
100 "params": { "name": "greet", "arguments": { "name": "World" } },
101 "_pqc": {
102 "signer_did": "did:pqaid:abcdef...",
103 "algorithm": "ML-DSA-65",
104 "timestamp": "2025-01-15T10:30:00+00:00",
105 "nonce": "a1b2c3d4e5f6...",
106 "signature": "3045022100...",
107 "public_key": "302a300506..."
108 }
109 }
110 ```
111
112 The `_pqc` field is stripped before signing (canonical form) and before passing the message to standard MCP handlers.
113
114 ### Signing Process
115
116 1. Remove `_pqc` from the message.
117 2. Serialize with `json.dumps(msg, sort_keys=True, separators=(',', ':'))`.
118 3. Compute `SHA3-256` hash of the canonical bytes.
119 4. Sign the hash with ML-DSA.
120 5. Attach the `_pqc` envelope with signature, public key, nonce, and timestamp.
121
122 ## Security Properties
123
124 | Property | Mechanism |
125 |---|---|
126 | **Message Authentication** | Every message is ML-DSA signed |
127 | **Mutual Authentication** | Both client and server verify each other during handshake |
128 | **Integrity** | Canonical JSON + SHA3-256 hash prevents tampering |
129 | **Replay Protection** | Per-session nonce tracking rejects duplicates |
130 | **Session Expiry** | Sessions have a configurable TTL (default: 1 hour) |
131 | **Quantum Resistance** | ML-DSA (FIPS 204) is resistant to Shor's algorithm |
132 | **Audit Trail** | Every operation is logged with signature metadata |
133
134 ## API Reference
135
136 ### `MessageSigner`
137
138 | Method | Description |
139 |---|---|
140 | `canonicalize(message)` | Deterministic JSON serialization (static) |
141 | `sign_message(message)` | Add `_pqc` envelope with ML-DSA signature |
142 | `verify_message(message)` | Verify `_pqc` envelope, returns `VerificationResult` (static) |
143 | `strip_pqc(message)` | Remove `_pqc` for standard MCP processing (static) |
144
145 ### `PQCHandshake`
146
147 | Method | Description |
148 |---|---|
149 | `initiate(identity)` | Create a signed handshake request |
150 | `respond(request, server_identity)` | Verify client, create signed response |
151 | `complete(response, client_identity, nonce)` | Verify server, create session |
152
153 ### `PQCSession`
154
155 | Method | Description |
156 |---|---|
157 | `is_valid()` | Check if session has not expired |
158 | `check_nonce(nonce)` | Register nonce, raise `ReplayAttackError` on reuse |
159 | `log_operation(...)` | Record operation in audit trail |
160 | `get_audit_log()` | Return list of `AuditEntry` records |
161
162 ### `PQCMCPClient`
163
164 | Method | Description |
165 |---|---|
166 | `connect()` | Perform PQC handshake, return `PQCSession` |
167 | `call_tool(name, arguments)` | Send signed tool call, verify response |
168 | `list_tools()` | List available tools (signed request) |
169 | `close()` | Close connection |
170
171 ### `PQCMCPServer`
172
173 | Method | Description |
174 |---|---|
175 | `tool(name, description)` | Decorator to register a tool handler |
176 | `handle_request(raw_message)` | Process incoming request with PQC verification |
177 | `handle_handshake(request)` | Handle handshake initiation |
178 | `get_tool_list()` | Return registered tools |
179 | `run(host, port)` | Start HTTP server |
180
181 ### `PQCMiddleware`
182
183 ASGI middleware for adding PQC to existing frameworks (Starlette, FastAPI):
184
185 ```python
186 from pqc_mcp_transport.middleware import PQCMiddleware
187 app = PQCMiddleware(app, server_identity=identity)
188 ```
189
190 ### Exceptions
191
192 | Exception | When |
193 |---|---|
194 | `PQCTransportError` | Base exception |
195 | `SignatureVerificationError` | Signature did not verify |
196 | `HandshakeError` | Handshake failed |
197 | `SessionExpiredError` | Session timed out |
198 | `ReplayAttackError` | Nonce reused |
199 | `PeerNotAuthenticatedError` | No handshake completed |
200
201 ## Examples
202
203 See the `examples/` directory:
204
205 - **`simple_server.py`** -- Run a PQC MCP server with signed responses
206 - **`simple_client.py`** -- Connect to a server with PQC handshake
207 - **`mutual_auth.py`** -- In-memory mutual authentication demo
208
209 ## Development
210
211 ```bash
212 # Install dev dependencies
213 pip install -e ".[dev]"
214
215 # Run tests
216 pytest
217
218 # Lint
219 ruff check src/ tests/
220 ```
221
222 ## Contributing
223
224 1. Fork the repository
225 2. Create a feature branch (`git checkout -b feature/my-feature`)
226 3. Write tests for your changes
227 4. Ensure all tests pass (`pytest`)
228 5. Submit a pull request
229
230 ## License
231
232 Apache License 2.0. See [LICENSE](LICENSE) for details.
233