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.
- package/index.js +1 -1
- package/package.json +1 -1
- 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.
|
|
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
package/websocket.js
CHANGED
|
@@ -1,15 +1,15 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* AAP WebSocket Server v3.
|
|
2
|
+
* AAP WebSocket Server v3.2
|
|
3
3
|
*
|
|
4
|
-
* Batch challenge
|
|
5
|
-
*
|
|
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.
|
|
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
|
-
|
|
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
|
-
|
|
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,
|