aap-agent-server 3.1.0 → 3.2.0

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.
Files changed (3) hide show
  1. package/index.js +1 -1
  2. package/package.json +1 -1
  3. package/websocket.js +75 -7
package/index.js CHANGED
@@ -24,7 +24,7 @@ export { createStore, createMemoryStore, createFileStore, createRedisStore } fro
24
24
  export * as logger from './logger.js';
25
25
 
26
26
  // Constants
27
- export const PROTOCOL_VERSION = '3.1.0';
27
+ export const PROTOCOL_VERSION = '3.2.0';
28
28
  export const TOTAL_TIME_MS = 6000;
29
29
  export const CHALLENGE_COUNT = 7;
30
30
  export const CONNECTION_TIMEOUT_MS = 60000;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "aap-agent-server",
3
- "version": "3.1.0",
3
+ "version": "3.2.0",
4
4
  "description": "WebSocket server for Agent Attestation Protocol - verify AI agents",
5
5
  "main": "index.js",
6
6
  "types": "index.d.ts",
package/websocket.js CHANGED
@@ -1,15 +1,15 @@
1
1
  /**
2
- * AAP WebSocket Server v3.1
2
+ * AAP WebSocket Server v3.2
3
3
  *
4
- * Batch challenge delivery over WebSocket.
5
- * All 7 challenges at once, 6 seconds total.
4
+ * Batch challenge + mandatory signature verification.
5
+ * No signature = no entry.
6
6
  */
7
7
 
8
8
  import { WebSocketServer } from 'ws';
9
- import { randomBytes, createHash } from 'node:crypto';
9
+ import { randomBytes, createHash, createVerify } from 'node:crypto';
10
10
 
11
11
  // ============== CONSTANTS ==============
12
- export const PROTOCOL_VERSION = '3.1.0';
12
+ export const PROTOCOL_VERSION = '3.2.0';
13
13
  export const CHALLENGE_COUNT = 7;
14
14
  export const TOTAL_TIME_MS = 6000;
15
15
  export const CONNECTION_TIMEOUT_MS = 60000;
@@ -110,6 +110,26 @@ function generateChallenge(nonce, index) {
110
110
  return { id: index, type, challenge: q, validate: v };
111
111
  }
112
112
 
113
+ /**
114
+ * Verify secp256k1 signature
115
+ */
116
+ function verifySignature(data, signature, publicKey) {
117
+ try {
118
+ const verifier = createVerify('SHA256');
119
+ verifier.update(data);
120
+ return verifier.verify(publicKey, signature, 'base64');
121
+ } catch {
122
+ return false;
123
+ }
124
+ }
125
+
126
+ /**
127
+ * Derive public ID from public key
128
+ */
129
+ function derivePublicId(publicKey) {
130
+ return createHash('sha256').update(publicKey).digest('hex').slice(0, 16);
131
+ }
132
+
113
133
  // ============== WEBSOCKET SERVER ==============
114
134
 
115
135
  /**
@@ -123,6 +143,7 @@ export function createAAPWebSocket(options = {}) {
123
143
  challengeCount = CHALLENGE_COUNT,
124
144
  totalTimeMs = TOTAL_TIME_MS,
125
145
  connectionTimeoutMs = CONNECTION_TIMEOUT_MS,
146
+ requireSignature = true, // v3.2: signature required by default
126
147
  onVerified,
127
148
  onFailed
128
149
  } = options;
@@ -137,6 +158,7 @@ export function createAAPWebSocket(options = {}) {
137
158
  let challenges = [];
138
159
  let validators = [];
139
160
  let challengesSentAt = null;
161
+ let publicKey = null;
140
162
  let publicId = null;
141
163
  let answered = false;
142
164
 
@@ -162,7 +184,8 @@ export function createAAPWebSocket(options = {}) {
162
184
  mode: 'batch',
163
185
  challengeCount,
164
186
  totalTimeMs,
165
- message: 'Send {"type":"ready"} to receive challenges.'
187
+ requireSignature,
188
+ message: 'Send {"type":"ready","publicKey":"..."} to receive challenges.'
166
189
  });
167
190
 
168
191
  ws.on('message', (data) => {
@@ -175,7 +198,14 @@ export function createAAPWebSocket(options = {}) {
175
198
  }
176
199
 
177
200
  if (msg.type === 'ready' && !challengesSentAt) {
178
- publicId = msg.publicId || 'anon-' + randomBytes(4).toString('hex');
201
+ // v3.2: publicKey required
202
+ if (requireSignature && !msg.publicKey) {
203
+ send(ws, { type: 'error', code: 'MISSING_PUBLIC_KEY', message: 'publicKey required for signature verification' });
204
+ return;
205
+ }
206
+
207
+ publicKey = msg.publicKey || null;
208
+ publicId = publicKey ? derivePublicId(publicKey) : 'anon-' + randomBytes(4).toString('hex');
179
209
  challengesSentAt = Date.now();
180
210
 
181
211
  // Send all challenges at once
@@ -193,6 +223,42 @@ export function createAAPWebSocket(options = {}) {
193
223
 
194
224
  const elapsed = Date.now() - challengesSentAt;
195
225
  const answers = msg.answers || [];
226
+ const signature = msg.signature;
227
+ const timestamp = msg.timestamp;
228
+
229
+ // v3.2: Verify signature first
230
+ if (requireSignature) {
231
+ if (!signature) {
232
+ const result = {
233
+ type: 'result',
234
+ verified: false,
235
+ message: 'Missing signature',
236
+ code: 'MISSING_SIGNATURE',
237
+ publicId
238
+ };
239
+ if (onFailed) onFailed(result);
240
+ send(ws, result);
241
+ setTimeout(() => ws.close(), 300);
242
+ return;
243
+ }
244
+
245
+ // Create proof data for verification
246
+ const proofData = JSON.stringify({ nonce, answers, publicId, timestamp });
247
+
248
+ if (!verifySignature(proofData, signature, publicKey)) {
249
+ const result = {
250
+ type: 'result',
251
+ verified: false,
252
+ message: 'Invalid signature',
253
+ code: 'INVALID_SIGNATURE',
254
+ publicId
255
+ };
256
+ if (onFailed) onFailed(result);
257
+ send(ws, result);
258
+ setTimeout(() => ws.close(), 300);
259
+ return;
260
+ }
261
+ }
196
262
 
197
263
  // Too slow?
198
264
  if (elapsed > totalTimeMs) {
@@ -200,6 +266,7 @@ export function createAAPWebSocket(options = {}) {
200
266
  type: 'result',
201
267
  verified: false,
202
268
  message: `Too slow: ${elapsed}ms > ${totalTimeMs}ms`,
269
+ code: 'TOO_SLOW',
203
270
  publicId,
204
271
  responseTimeMs: elapsed
205
272
  };
@@ -236,6 +303,7 @@ export function createAAPWebSocket(options = {}) {
236
303
 
237
304
  verifiedTokens.set(result.sessionToken, {
238
305
  publicId,
306
+ publicKey,
239
307
  nonce,
240
308
  verifiedAt: Date.now(),
241
309
  expiresAt: Date.now() + 3600000,