@agentunion/fastaun-browser 0.5.18 → 0.5.20
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/CHANGELOG.md +172 -79
- package/_packed_docs/CHANGELOG.md +172 -79
- package/_packed_docs/INDEX.md +244 -116
- package/_packed_docs/KITE_DOCS_GUIDE.md +98 -69
- package/_packed_docs/agent.md/SCHEMA.md +245 -205
- package/_packed_docs/agent.md//350/277/234/347/250/213agent.md/347/274/223/345/255/230/344/270/216etag/351/200/217/344/274/240/346/226/271/346/241/210.md +11 -11
- package/_packed_docs/aun/345/210/206/345/270/203/345/274/217/346/265/213/350/257/225/350/277/220/350/241/214/346/214/207/345/215/227.md +42 -15
- package/_packed_docs/aun/346/265/213/350/257/225/350/277/220/350/241/214/346/214/207/345/215/227.md +4 -4
- package/_packed_docs/protocol/01-/350/272/253/344/273/275/344/270/216/345/207/255/350/257/201/345/215/217/350/256/256-auth.md +45 -134
- package/_packed_docs/protocol/06-/346/234/215/345/212/241/345/215/217/350/256/256.md +1072 -1072
- package/_packed_docs/protocol/07-/351/224/231/350/257/257/347/240/201/344/270/216/347/212/266/346/200/201/346/234/272.md +5 -20
- package/_packed_docs/protocol/10-Group-/345/255/220/345/215/217/350/256/256.md +687 -668
- package/_packed_docs/protocol/11-Storage-/345/255/220/345/215/217/350/256/256.md +19 -28
- package/_packed_docs/protocol/Ownership/345/215/217/350/256/256/345/256/232/344/271/211.md +443 -0
- package/_packed_docs/protocol/Ownership/345/215/217/350/256/256/345/256/232/344/271/2112.0.md +161 -0
- package/_packed_docs/protocol/Ownership/345/215/217/350/256/256/345/256/232/344/271/2112.1.md +658 -0
- package/_packed_docs/protocol/Ownership/345/215/217/350/256/256/345/256/232/344/271/2113.0.md +233 -0
- package/_packed_docs/protocol/agent.md/SCHEMA.md +4 -0
- package/_packed_docs/sdk/03-/346/240/270/345/277/203/346/246/202/345/277/265.md +1 -1
- package/_packed_docs/sdk/04-/350/277/236/346/216/245/344/270/216/350/256/244/350/257/201.md +68 -61
- package/_packed_docs/sdk/05-E2EE/345/212/240/345/257/206/351/200/232/344/277/241.md +1 -1
- package/_packed_docs/sdk/06-API/346/211/213/345/206/214.md +279 -285
- package/_packed_docs/sdk/08-/346/234/200/344/275/263/345/256/236/350/267/265.md +3 -3
- package/_packed_docs/sdk/09-group-rpc-manual.md +271 -235
- package/_packed_docs/sdk/09-message-rpc-manual.md +47 -6
- package/_packed_docs/sdk/09-storage-rpc-manual.md +70 -68
- package/_packed_docs/sdk/AUN_DOCS_GUIDE.md +63 -34
- package/_packed_docs/sdk/INDEX.md +105 -34
- package/_packed_docs/sdk/Ownership-SDK/345/256/236/347/216/260/346/214/207/345/215/227.md +169 -0
- package/_packed_docs/sdk/Ownership-SDK/347/274/226/346/216/222/346/214/207/345/215/227.md +349 -0
- package/_packed_docs/sdk/Ownership-SDK/347/274/226/346/216/222/346/214/207/345/215/2272.1.md +922 -0
- package/_packed_docs/sdk/Ownership-SDK/351/233/206/346/210/220/346/214/207/345/215/227.md +270 -0
- package/_packed_docs/sdk/Ownership2.1-SDK/345/272/224/347/224/250/351/233/206/346/210/220/346/214/207/345/215/227.md +300 -0
- package/_packed_docs/sdk/Ownership2.1-SDK/345/274/200/345/217/221/344/270/216/347/247/273/346/244/215/346/214/207/345/215/227.md +382 -0
- package/_packed_docs/sdk/Ownership2.1-TDD/345/210/206/351/230/266/346/256/265/345/256/236/346/226/275/350/256/241/345/210/222.md +708 -0
- package/_packed_docs/sdk/Ownership2.1/350/257/246/347/273/206/350/256/276/350/256/241.md +476 -0
- package/_packed_docs/sdk/Ownership/347/256/241/347/220/206/344/270/223/351/242/230.md +559 -0
- package/_packed_docs/sdk/ProtectedHeaders/347/224/250/346/263/225/344/270/223/351/242/230.md +299 -0
- package/_packed_docs/sdk//345/207/255/350/257/201/344/270/216/347/273/204/347/273/207/346/234/215/345/212/241SDK/345/272/224/347/224/250/351/233/206/346/210/220/346/214/207/345/215/227.md +173 -0
- package/_packed_docs/sdk//345/207/255/350/257/201/344/270/216/347/273/204/347/273/207/346/234/215/345/212/241SDK/345/274/200/345/217/221/344/270/216/347/247/273/346/244/215/346/214/207/345/215/227.md +211 -0
- package/_packed_docs//350/267/250/350/256/276/345/244/207/345/267/262/350/257/273/346/234/252/350/257/273SDK/345/256/236/347/216/260/347/247/273/346/244/215/346/214/207/345/215/227.md +331 -0
- package/_packed_docs//350/267/250/350/256/276/345/244/207/345/267/262/350/257/273/346/234/252/350/257/273SDK/351/233/206/346/210/220/346/214/207/345/215/227.md +207 -0
- package/_packed_docs//350/267/250/350/256/276/345/244/207/345/267/262/350/257/273/346/234/252/350/257/273/344/273/243/347/240/201/345/256/241/346/237/245/346/212/245/345/221/212.md +252 -0
- package/_packed_docs//350/267/250/350/256/276/345/244/207/345/267/262/350/257/273/346/234/252/350/257/273/345/256/236/346/226/275/350/256/241/345/210/222.md +156 -0
- package/_packed_docs//350/267/250/350/256/276/345/244/207/345/267/262/350/257/273/346/234/252/350/257/273/346/226/271/346/241/210.md +273 -0
- package/_packed_docs//350/267/250/350/256/276/345/244/207/345/267/262/350/257/273/346/234/252/350/257/273/350/257/246/347/273/206/350/256/276/350/256/241.md +211 -0
- package/dist/agent-md-schema.d.ts +17 -3
- package/dist/agent-md-schema.d.ts.map +1 -1
- package/dist/agent-md-schema.js +94 -26
- package/dist/agent-md-schema.js.map +1 -1
- package/dist/agent-md.d.ts +54 -3
- package/dist/agent-md.d.ts.map +1 -1
- package/dist/agent-md.js +711 -65
- package/dist/agent-md.js.map +1 -1
- package/dist/aid-store.d.ts +58 -46
- package/dist/aid-store.d.ts.map +1 -1
- package/dist/aid-store.js +254 -248
- package/dist/aid-store.js.map +1 -1
- package/dist/aid.d.ts +4 -1
- package/dist/aid.d.ts.map +1 -1
- package/dist/aid.js +27 -33
- package/dist/aid.js.map +1 -1
- package/dist/auth.d.ts +4 -1
- package/dist/auth.d.ts.map +1 -1
- package/dist/auth.js +426 -39
- package/dist/auth.js.map +1 -1
- package/dist/bundle.js +38179 -32054
- package/dist/cert-utils.d.ts +50 -2
- package/dist/cert-utils.d.ts.map +1 -1
- package/dist/cert-utils.js +105 -47
- package/dist/cert-utils.js.map +1 -1
- package/dist/client/delivery.d.ts +22 -1
- package/dist/client/delivery.d.ts.map +1 -1
- package/dist/client/delivery.js +389 -78
- package/dist/client/delivery.js.map +1 -1
- package/dist/client/rpc-pipeline.d.ts.map +1 -1
- package/dist/client/rpc-pipeline.js +103 -77
- package/dist/client/rpc-pipeline.js.map +1 -1
- package/dist/client/v2-e2ee.d.ts +1 -0
- package/dist/client/v2-e2ee.d.ts.map +1 -1
- package/dist/client/v2-e2ee.js +182 -89
- package/dist/client/v2-e2ee.js.map +1 -1
- package/dist/client.d.ts +100 -1
- package/dist/client.d.ts.map +1 -1
- package/dist/client.js +1220 -25
- package/dist/client.js.map +1 -1
- package/dist/crypto.d.ts.map +1 -1
- package/dist/crypto.js +8 -6
- package/dist/crypto.js.map +1 -1
- package/dist/errors.d.ts.map +1 -1
- package/dist/errors.js +3 -2
- package/dist/errors.js.map +1 -1
- package/dist/events.d.ts +9 -2
- package/dist/events.d.ts.map +1 -1
- package/dist/events.js +72 -13
- package/dist/events.js.map +1 -1
- package/dist/facades.d.ts +82 -2
- package/dist/facades.d.ts.map +1 -1
- package/dist/facades.js +100 -6
- package/dist/facades.js.map +1 -1
- package/dist/index.d.ts +7 -3
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +3 -2
- package/dist/index.js.map +1 -1
- package/dist/keystore/index.d.ts +17 -0
- package/dist/keystore/index.d.ts.map +1 -1
- package/dist/keystore/indexeddb-shared.d.ts +10 -1
- package/dist/keystore/indexeddb-shared.d.ts.map +1 -1
- package/dist/keystore/indexeddb-shared.js +111 -0
- package/dist/keystore/indexeddb-shared.js.map +1 -1
- package/dist/keystore/indexeddb-token-store.d.ts +7 -1
- package/dist/keystore/indexeddb-token-store.d.ts.map +1 -1
- package/dist/keystore/indexeddb-token-store.js +30 -1
- package/dist/keystore/indexeddb-token-store.js.map +1 -1
- package/dist/org-protocol.d.ts +98 -0
- package/dist/org-protocol.d.ts.map +1 -0
- package/dist/org-protocol.js +1802 -0
- package/dist/org-protocol.js.map +1 -0
- package/dist/ownership-proof.d.ts +93 -5
- package/dist/ownership-proof.d.ts.map +1 -1
- package/dist/ownership-proof.js +239 -53
- package/dist/ownership-proof.js.map +1 -1
- package/dist/ownership-v21.d.ts +194 -0
- package/dist/ownership-v21.d.ts.map +1 -0
- package/dist/ownership-v21.js +678 -0
- package/dist/ownership-v21.js.map +1 -0
- package/dist/ownership-workflow.d.ts +151 -0
- package/dist/ownership-workflow.d.ts.map +1 -0
- package/dist/ownership-workflow.js +1229 -0
- package/dist/ownership-workflow.js.map +1 -0
- package/dist/seq-tracker.d.ts +1 -0
- package/dist/seq-tracker.d.ts.map +1 -1
- package/dist/seq-tracker.js +10 -2
- package/dist/seq-tracker.js.map +1 -1
- package/dist/tools/cross-sdk-agent.d.ts +83 -0
- package/dist/tools/cross-sdk-agent.d.ts.map +1 -1
- package/dist/tools/cross-sdk-agent.js +415 -7
- package/dist/tools/cross-sdk-agent.js.map +1 -1
- package/dist/transport.d.ts +7 -0
- package/dist/transport.d.ts.map +1 -1
- package/dist/transport.js +90 -2
- package/dist/transport.js.map +1 -1
- package/dist/v2/e2ee/decrypt.d.ts +1 -0
- package/dist/v2/e2ee/decrypt.d.ts.map +1 -1
- package/dist/v2/e2ee/decrypt.js +9 -1
- package/dist/v2/e2ee/decrypt.js.map +1 -1
- package/dist/version.d.ts +1 -1
- package/dist/version.js +1 -1
- package/package.json +1 -1
package/dist/aid-store.js
CHANGED
|
@@ -1,17 +1,37 @@
|
|
|
1
1
|
import { AID } from './aid.js';
|
|
2
|
-
import { CryptoProvider, pemToArrayBuffer, importPrivateKeyEcdsa, importCertPublicKeyEcdsa, ecdsaSignDer, ecdsaVerifyDer } from './crypto.js';
|
|
2
|
+
import { CryptoProvider, pemToArrayBuffer, importPrivateKeyEcdsa, importCertPublicKeyEcdsa, ecdsaSignDer, ecdsaVerifyDer, certificateSha256Fingerprint } from './crypto.js';
|
|
3
3
|
import * as codes from './error-codes.js';
|
|
4
|
-
import { certMatchesFingerprint, publicKeyDerB64 } from './cert-utils.js';
|
|
4
|
+
import { certMatchesFingerprint, parseAgentMdTailSignatureForRead, publicKeyDerB64, signDocument, verifyDocument, parseCertMetadata } from './cert-utils.js';
|
|
5
5
|
import { RegisterFlow } from './register-flow.js';
|
|
6
6
|
import { GatewayDiscovery } from './discovery.js';
|
|
7
|
-
import { ClientSignatureError, IdentityConflictError, NotFoundError, ValidationError } from './errors.js';
|
|
7
|
+
import { AUNError, ClientSignatureError, IdentityConflictError, NotFoundError, StateError, ValidationError } from './errors.js';
|
|
8
8
|
import { IndexedDBIdentityStore } from './keystore/indexeddb-identity-store.js';
|
|
9
9
|
import { IndexedDBTokenStore } from './keystore/indexeddb-token-store.js';
|
|
10
10
|
import { getDeviceId, normalizeSlotId } from './config.js';
|
|
11
11
|
import { resultErr, resultOk } from './result.js';
|
|
12
12
|
import { AgentMdManager, buildDefaultAgentMd } from './agent-md.js';
|
|
13
13
|
import { AuthFlow } from './auth.js';
|
|
14
|
-
import {
|
|
14
|
+
import { parseOwnershipRegistrationIntent } from './ownership-workflow.js';
|
|
15
|
+
import { validateAIDFormat } from './validators.js';
|
|
16
|
+
import { validateAgentMdDocument } from './agent-md-schema.js';
|
|
17
|
+
async function readSigningInput(input, inputType = 'text') {
|
|
18
|
+
if (inputType === 'text') {
|
|
19
|
+
if (typeof input === 'string' || input instanceof Uint8Array)
|
|
20
|
+
return input;
|
|
21
|
+
throw new Error('text input requires a string or Uint8Array');
|
|
22
|
+
}
|
|
23
|
+
if (typeof input === 'string' || input instanceof Uint8Array)
|
|
24
|
+
throw new Error('browser file input does not support filesystem paths');
|
|
25
|
+
const file = 'getFile' in input ? await input.getFile() : input;
|
|
26
|
+
if (typeof file.arrayBuffer === 'function')
|
|
27
|
+
return new Uint8Array(await file.arrayBuffer());
|
|
28
|
+
return await new Promise((resolve, reject) => {
|
|
29
|
+
const reader = new FileReader();
|
|
30
|
+
reader.onload = () => resolve(new Uint8Array(reader.result));
|
|
31
|
+
reader.onerror = () => reject(reader.error);
|
|
32
|
+
reader.readAsArrayBuffer(file);
|
|
33
|
+
});
|
|
34
|
+
}
|
|
15
35
|
// ── 证书 DER 解析工具 ────────────────────────────────────────────
|
|
16
36
|
function _derReadLength(data, offset) {
|
|
17
37
|
if (offset >= data.length)
|
|
@@ -266,6 +286,8 @@ function resultError(result) {
|
|
|
266
286
|
return result.ok ? null : result.error;
|
|
267
287
|
}
|
|
268
288
|
function agentMdFailure(exc) {
|
|
289
|
+
if (exc instanceof AUNError && exc.stringCode)
|
|
290
|
+
return resultErr(exc.stringCode, exc.message, exc);
|
|
269
291
|
if (exc instanceof ClientSignatureError) {
|
|
270
292
|
return resultErr(codes.SIGNATURE_INVALID, exc.message, exc);
|
|
271
293
|
}
|
|
@@ -277,19 +299,6 @@ function agentMdFailure(exc) {
|
|
|
277
299
|
}
|
|
278
300
|
return resultErr(codes.NETWORK_ERROR, exc instanceof Error ? exc.message : String(exc), exc);
|
|
279
301
|
}
|
|
280
|
-
function ownerAgentMd(content, ownerAid, proof) {
|
|
281
|
-
const marker = content.indexOf('<!-- AUN-SIGNATURE');
|
|
282
|
-
const payload = marker >= 0 ? content.slice(0, marker) : content;
|
|
283
|
-
const lines = payload.replace(/\r\n/g, '\n').replace(/\r/g, '\n').split('\n');
|
|
284
|
-
const end = lines.findIndex((line, index) => index > 0 && line.trim() === '---');
|
|
285
|
-
if (end < 0)
|
|
286
|
-
throw new ValidationError('agent.md frontmatter is required for ownership operation');
|
|
287
|
-
const filtered = lines.filter((line, index) => !(index > 0 && index < end && (/^\s*owner\s*:/.test(line) || /^\s*ownership_proof\s*:/.test(line))));
|
|
288
|
-
const closed = filtered.findIndex((line, index) => index > 0 && line.trim() === '---');
|
|
289
|
-
filtered.splice(closed, 0, `owner: ${JSON.stringify(ownerAid)}`, `ownership_proof: ${JSON.stringify(proof)}`);
|
|
290
|
-
const result = filtered.join('\n');
|
|
291
|
-
return result.endsWith('\n') ? result : `${result}\n`;
|
|
292
|
-
}
|
|
293
302
|
export class AIDStore {
|
|
294
303
|
aunPath;
|
|
295
304
|
deviceId;
|
|
@@ -337,10 +346,15 @@ export class AIDStore {
|
|
|
337
346
|
tokenStore: this._tokenStore,
|
|
338
347
|
aidValidator: (target) => this._registerFlow.validateAidName(target),
|
|
339
348
|
gatewayResolver: (target) => this._resolveGateway(target),
|
|
340
|
-
|
|
349
|
+
entityOwnerResolver: (target, type) => this._resolveEntityAgentMdOwner(target, type),
|
|
350
|
+
entityIdentityGetter: (target, type) => this._currentEntityCertificate(target, type ?? 'group'),
|
|
351
|
+
entitySignerResolver: (target, fingerprint) => this._currentEntitySigner(target, fingerprint),
|
|
352
|
+
peerResolver: async (target, certFingerprint, signedAt) => {
|
|
341
353
|
const fp = String(certFingerprint ?? '').trim().toLowerCase();
|
|
342
354
|
const cachedCert = await this._keystore.loadCert(target, fp || undefined);
|
|
343
355
|
if (cachedCert) {
|
|
356
|
+
const gatewayUrl = await this._resolveGateway(target);
|
|
357
|
+
await this._verifyDownloadedPeerCertificate(gatewayUrl, target, cachedCert, fp, signedAt);
|
|
344
358
|
return await AID.create({ aid: target, aunPath: this.aunPath, certPem: cachedCert, privateKeyPem: null, certValid: true, privateKeyValid: false });
|
|
345
359
|
}
|
|
346
360
|
if (fp) {
|
|
@@ -349,7 +363,7 @@ export class AIDStore {
|
|
|
349
363
|
if (!response.ok)
|
|
350
364
|
throw new Error(`certificate not found for aid: ${target}`);
|
|
351
365
|
const certPem = await response.text();
|
|
352
|
-
await this._verifyDownloadedPeerCertificate(gatewayUrl, target, certPem, fp);
|
|
366
|
+
await this._verifyDownloadedPeerCertificate(gatewayUrl, target, certPem, fp, signedAt);
|
|
353
367
|
await this._keystore.saveCert(target, certPem, fp, { makeActive: false });
|
|
354
368
|
return await AID.create({ aid: target, aunPath: this.aunPath, certPem, privateKeyPem: null, certValid: true, privateKeyValid: false });
|
|
355
369
|
}
|
|
@@ -357,7 +371,7 @@ export class AIDStore {
|
|
|
357
371
|
const certPem = await this._registerFlow.fetchPeerCert(gatewayUrl, target);
|
|
358
372
|
if (!certPem)
|
|
359
373
|
throw new Error(`certificate not found for aid: ${target}`);
|
|
360
|
-
await this._verifyDownloadedPeerCertificate(gatewayUrl, target, certPem);
|
|
374
|
+
await this._verifyDownloadedPeerCertificate(gatewayUrl, target, certPem, undefined, signedAt);
|
|
361
375
|
await this._keystore.saveCert(target, certPem);
|
|
362
376
|
return await AID.create({ aid: target, aunPath: this.aunPath, certPem, privateKeyPem: null, certValid: true, privateKeyValid: false });
|
|
363
377
|
},
|
|
@@ -371,6 +385,76 @@ export class AIDStore {
|
|
|
371
385
|
if (typeof closeTokens === 'function')
|
|
372
386
|
closeTokens.call(this._tokenStore);
|
|
373
387
|
}
|
|
388
|
+
async sign(aid, input, options = {}) {
|
|
389
|
+
try {
|
|
390
|
+
if (options.inputType !== undefined && !['text', 'file'].includes(options.inputType) || options.format !== undefined && !['raw', 'document'].includes(options.format))
|
|
391
|
+
throw new Error('unsupported signing inputType or format');
|
|
392
|
+
if (options.append && options.format !== 'document')
|
|
393
|
+
throw new Error('append requires document format');
|
|
394
|
+
if (options.inPlace && (options.inputType !== 'file' || options.format !== 'document' || typeof input !== 'object' || !('createWritable' in input) || typeof input.createWritable !== 'function'))
|
|
395
|
+
throw new Error('inPlace document signing requires a writable file handle');
|
|
396
|
+
const loaded = options.identity ? resultOk({ aid: options.identity }) : await this.load(aid);
|
|
397
|
+
if (!loaded.ok)
|
|
398
|
+
return loaded;
|
|
399
|
+
if (loaded.data.aid.aid !== aid || parseCertMetadata(loaded.data.aid.certPem).subject.toLowerCase() !== aid.toLowerCase())
|
|
400
|
+
throw new Error('signing identity does not match AID');
|
|
401
|
+
const raw = await readSigningInput(input, options.inputType);
|
|
402
|
+
if (typeof raw === 'string' && new TextDecoder('utf-8', { fatal: true, ignoreBOM: true }).decode(new TextEncoder().encode(raw)) !== raw)
|
|
403
|
+
throw new Error('signing input must be valid UTF-8');
|
|
404
|
+
if (options.format !== 'document')
|
|
405
|
+
return await loaded.data.aid.sign(raw);
|
|
406
|
+
const content = typeof raw === 'string' ? raw : new TextDecoder('utf-8', { fatal: true, ignoreBOM: true }).decode(raw);
|
|
407
|
+
const result = await signDocument(loaded.data.aid, content, options);
|
|
408
|
+
if (options.inPlace) {
|
|
409
|
+
const writable = await input.createWritable();
|
|
410
|
+
await writable.write(result.signed);
|
|
411
|
+
await writable.close();
|
|
412
|
+
}
|
|
413
|
+
return resultOk(result);
|
|
414
|
+
}
|
|
415
|
+
catch (exc) {
|
|
416
|
+
return resultErr(codes.SIGNATURE_OPERATION_ERROR, String(exc), exc);
|
|
417
|
+
}
|
|
418
|
+
}
|
|
419
|
+
async verify(aid, input, signatureOrOptions = {}, options = {}) {
|
|
420
|
+
try {
|
|
421
|
+
const opts = typeof signatureOrOptions === 'string' ? options : signatureOrOptions;
|
|
422
|
+
if (opts.inputType !== undefined && !['text', 'file'].includes(opts.inputType) || opts.format !== undefined && !['raw', 'document'].includes(opts.format))
|
|
423
|
+
throw new Error('unsupported verification inputType or format');
|
|
424
|
+
if (opts.identity && (opts.identity.aid !== aid || parseCertMetadata(opts.identity.certPem).subject.toLowerCase() !== aid.toLowerCase()))
|
|
425
|
+
throw new Error('verification identity does not match AID');
|
|
426
|
+
const raw = await readSigningInput(input, opts.inputType);
|
|
427
|
+
if (typeof raw === 'string' && new TextDecoder('utf-8', { fatal: true, ignoreBOM: true }).decode(new TextEncoder().encode(raw)) !== raw)
|
|
428
|
+
throw new Error('verification input must be valid UTF-8');
|
|
429
|
+
if (opts.format === 'document') {
|
|
430
|
+
const content = typeof raw === 'string' ? raw : new TextDecoder('utf-8', { fatal: true, ignoreBOM: true }).decode(raw);
|
|
431
|
+
const resolver = opts.certificateResolver ?? (async (signer, fingerprint) => {
|
|
432
|
+
if (opts.identity?.aid === signer)
|
|
433
|
+
return opts.identity;
|
|
434
|
+
const resolved = await this.load(signer);
|
|
435
|
+
if (!resolved.ok)
|
|
436
|
+
throw new Error(resolved.error.message);
|
|
437
|
+
return resolved.data.aid;
|
|
438
|
+
});
|
|
439
|
+
return resultOk(await verifyDocument(content, resolver, { expectedAid: aid }));
|
|
440
|
+
}
|
|
441
|
+
if (typeof signatureOrOptions !== 'string')
|
|
442
|
+
throw new Error('raw verification requires a detached signature');
|
|
443
|
+
const supplied = !opts.identity && opts.certificateResolver ? await opts.certificateResolver(aid, '', 0) : undefined;
|
|
444
|
+
if (supplied && typeof supplied !== 'string' && 'aid' in supplied && supplied.aid !== aid)
|
|
445
|
+
throw new Error('verification certificate identity does not match AID');
|
|
446
|
+
const suppliedPem = typeof supplied === 'string' ? supplied : supplied?.certPem;
|
|
447
|
+
const loaded = opts.identity ? resultOk({ aid: opts.identity }) : suppliedPem ? resultOk({ aid: await AID.create({ aid, aunPath: this.aunPath, certPem: suppliedPem, privateKeyPem: null, certValid: true, privateKeyValid: false }) }) : await this.load(aid);
|
|
448
|
+
if (!loaded.ok)
|
|
449
|
+
return loaded;
|
|
450
|
+
if (loaded.data.aid.aid !== aid || parseCertMetadata(loaded.data.aid.certPem).subject.toLowerCase() !== aid.toLowerCase())
|
|
451
|
+
throw new Error('verification identity does not match AID');
|
|
452
|
+
return await loaded.data.aid.verify(raw, signatureOrOptions);
|
|
453
|
+
}
|
|
454
|
+
catch (exc) {
|
|
455
|
+
return resultErr(codes.VERIFICATION_OPERATION_ERROR, String(exc), exc);
|
|
456
|
+
}
|
|
457
|
+
}
|
|
374
458
|
async load(aid) {
|
|
375
459
|
const target = String(aid ?? '').trim();
|
|
376
460
|
const certPem = await this._keystore.loadCert(target);
|
|
@@ -386,6 +470,9 @@ export class AIDStore {
|
|
|
386
470
|
catch (exc) {
|
|
387
471
|
return resultErr(codes.CERT_PARSE_ERROR, `certificate parse failed for aid: ${target}`, exc);
|
|
388
472
|
}
|
|
473
|
+
if (await this._tokenStore.getMetadata?.(target, 'invalidated_entity_certificate') === await certificateSha256Fingerprint(certPem)) {
|
|
474
|
+
return resultErr(codes.CERT_NOT_VALID, `group/org identity has been invalidated: ${target}`);
|
|
475
|
+
}
|
|
389
476
|
// 有效期检查(对齐 Python cert_time_error)
|
|
390
477
|
const validity = parseCertValidity(certPem);
|
|
391
478
|
if (!validity) {
|
|
@@ -492,12 +579,26 @@ export class AIDStore {
|
|
|
492
579
|
listAsync() {
|
|
493
580
|
return this.list();
|
|
494
581
|
}
|
|
495
|
-
async
|
|
582
|
+
async register(aid, options) {
|
|
496
583
|
const target = String(aid ?? '').trim();
|
|
497
584
|
try {
|
|
498
585
|
if (!target)
|
|
499
586
|
throw new ValidationError("AIDStore.register requires 'aid'");
|
|
500
587
|
this._registerFlow.validateAidName(target);
|
|
588
|
+
const type = String(options?.type ?? '').trim(), owner = String(options?.owner ?? '').trim().toLowerCase();
|
|
589
|
+
const entity = ['group', 'org'].includes(type);
|
|
590
|
+
if (!type || type === 'human' && owner || entity && !owner)
|
|
591
|
+
throw new ValidationError('register requires type; human forbids owner; group/org requires owner');
|
|
592
|
+
if (owner)
|
|
593
|
+
validateAIDFormat(owner, 'owner');
|
|
594
|
+
const previousRaw = await this._tokenStore.getMetadata(target, 'registration_intent');
|
|
595
|
+
const previous = previousRaw ? parseOwnershipRegistrationIntent(previousRaw, target) : null;
|
|
596
|
+
const exttype = String(options?.exttype ?? '').trim();
|
|
597
|
+
if (previous && (previous.type !== type || previous.owner !== owner || previous.exttype !== exttype))
|
|
598
|
+
throw new StateError('ownership registration intent changed', { stringCode: 'conflict' });
|
|
599
|
+
const intent = previous ?? (entity ? { schema_version: 1, sub_aid: target, type, owner, exttype, protocol: 'entity', pending: false } : { schema_version: 1, sub_aid: target, type, owner, exttype, operation_id: crypto.randomUUID(), pending: Boolean(owner) });
|
|
600
|
+
if (type !== 'agent' || owner)
|
|
601
|
+
await this._tokenStore.setMetadata(target, 'registration_intent', JSON.stringify(intent));
|
|
501
602
|
const gatewayUrl = await this._resolveGateway(target);
|
|
502
603
|
const result = await this._registerFlow.registerAid(gatewayUrl, target);
|
|
503
604
|
if (result.cert) {
|
|
@@ -511,11 +612,16 @@ export class AIDStore {
|
|
|
511
612
|
});
|
|
512
613
|
}
|
|
513
614
|
await this._persistGatewayUrl(target, gatewayUrl);
|
|
514
|
-
if (
|
|
515
|
-
await this.
|
|
615
|
+
if (!entity) {
|
|
616
|
+
const snapshot = await this._agentMdManager.readOwnershipFresh(target).catch(() => null);
|
|
617
|
+
if (snapshot && !snapshot.exists && snapshot.relation_version === 0 && snapshot.lock === 'unlocked')
|
|
618
|
+
await this.uploadAgentMd(target, buildDefaultAgentMd(target, { type, exttype: intent.exttype }));
|
|
619
|
+
}
|
|
516
620
|
return resultOk({ registered: true });
|
|
517
621
|
}
|
|
518
622
|
catch (exc) {
|
|
623
|
+
if (exc instanceof StateError && exc.stringCode)
|
|
624
|
+
return resultErr(exc.stringCode, exc.message, exc);
|
|
519
625
|
if (exc instanceof IdentityConflictError) {
|
|
520
626
|
return resultErr(codes.IDENTITY_CONFLICT, String(exc), exc);
|
|
521
627
|
}
|
|
@@ -529,218 +635,6 @@ export class AIDStore {
|
|
|
529
635
|
return resultErr(codes.SERVER_ERROR, message, exc);
|
|
530
636
|
}
|
|
531
637
|
}
|
|
532
|
-
async register(aidOrParams) {
|
|
533
|
-
if (typeof aidOrParams === 'string')
|
|
534
|
-
return await this.registerIdentity(aidOrParams);
|
|
535
|
-
const params = aidOrParams;
|
|
536
|
-
const target = String(params?.aid ?? '').trim();
|
|
537
|
-
const aidType = String(params?.type ?? '').trim().toLowerCase();
|
|
538
|
-
const owner = String(params?.owner ?? params?.owner_aid ?? '').trim();
|
|
539
|
-
if (!target || !aidType)
|
|
540
|
-
return resultErr(codes.INVALID_AID_FORMAT, 'register requires aid and type');
|
|
541
|
-
try {
|
|
542
|
-
const identity = await this.registerIdentity(target, false);
|
|
543
|
-
if (!identity.ok)
|
|
544
|
-
return identity;
|
|
545
|
-
const gatewayUrl = await this._resolveGateway(target);
|
|
546
|
-
const prepareParams = { aid: target, type: aidType };
|
|
547
|
-
if (owner)
|
|
548
|
-
prepareParams.owner = owner;
|
|
549
|
-
const idempotencyKey = params.idempotencyKey ?? params.idempotency_key;
|
|
550
|
-
if (idempotencyKey)
|
|
551
|
-
prepareParams.idempotency_key = idempotencyKey;
|
|
552
|
-
const prepared = await this._registerFlow.shortRpc(gatewayUrl, 'auth.aid_register_prepare', prepareParams);
|
|
553
|
-
const operationId = String(prepared.operation_id ?? '').trim();
|
|
554
|
-
if (!operationId)
|
|
555
|
-
throw new ValidationError('aid_register_prepare response missing operation_id');
|
|
556
|
-
let subjectSignature = params.subjectSignature ?? params.subject_signature;
|
|
557
|
-
const ownerSignature = params.ownerSignature ?? params.owner_signature;
|
|
558
|
-
if (!subjectSignature && owner) {
|
|
559
|
-
const loaded = await this.load(target);
|
|
560
|
-
if (loaded.ok) {
|
|
561
|
-
const statement = OwnershipStatement.create(String(prepared.proof_id ?? ''), target, owner);
|
|
562
|
-
const signed = await signOwnershipStatement({ certFingerprint: loaded.data.aid.certFingerprint, sign: (payload) => loaded.data.aid.sign(payload) }, statement);
|
|
563
|
-
subjectSignature = { cert_fingerprint: signed.cert_fingerprint, signature: signed.signature };
|
|
564
|
-
}
|
|
565
|
-
}
|
|
566
|
-
const signatures = {};
|
|
567
|
-
if (subjectSignature)
|
|
568
|
-
signatures.subject_signature = subjectSignature;
|
|
569
|
-
if (ownerSignature)
|
|
570
|
-
signatures.owner_signature = ownerSignature;
|
|
571
|
-
let response = prepared;
|
|
572
|
-
if (Object.keys(signatures).length > 0)
|
|
573
|
-
response = await this._registerFlow.shortRpc(gatewayUrl, 'auth.aid_register_submit', { operation_id: operationId, ...signatures });
|
|
574
|
-
const state = String(response.state ?? response.operation_state ?? '').toLowerCase();
|
|
575
|
-
const complete = !owner || owner === target || (!!ownerSignature && !!subjectSignature);
|
|
576
|
-
if (complete && state === 'agent_md_pending') {
|
|
577
|
-
let content = params.agentMd ?? params.agent_md ?? buildDefaultAgentMd(target, { type: aidType });
|
|
578
|
-
if (owner) {
|
|
579
|
-
const proof = { proof_id: prepared.proof_id, subject_aid: target, owner_aid: owner, fact_hash: prepared.fact_hash, statement_hash: prepared.statement_hash, subject_signature: subjectSignature };
|
|
580
|
-
if (owner !== target)
|
|
581
|
-
proof.owner_signature = ownerSignature;
|
|
582
|
-
content = ownerAgentMd(content, owner, proof);
|
|
583
|
-
}
|
|
584
|
-
const uploaded = await this.uploadAgentMd(target, content, { ownerOperationId: operationId });
|
|
585
|
-
if (!uploaded.ok)
|
|
586
|
-
return uploaded;
|
|
587
|
-
response = await this._registerFlow.shortRpc(gatewayUrl, 'auth.aid_register_activate', { operation_id: operationId });
|
|
588
|
-
}
|
|
589
|
-
return resultOk(response);
|
|
590
|
-
}
|
|
591
|
-
catch (exc) {
|
|
592
|
-
if (exc instanceof ValidationError)
|
|
593
|
-
return resultErr(codes.INVALID_AID_FORMAT, exc.message, exc);
|
|
594
|
-
return resultErr(codes.NETWORK_ERROR, exc instanceof Error ? exc.message : String(exc), exc);
|
|
595
|
-
}
|
|
596
|
-
}
|
|
597
|
-
async create(params) { return await this.register(params); }
|
|
598
|
-
async bindAid(params) {
|
|
599
|
-
const target = String(params?.aid ?? '').trim();
|
|
600
|
-
const aidType = String(params?.type ?? '').trim().toLowerCase();
|
|
601
|
-
const owner = String(params?.owner ?? params?.owner_aid ?? '').trim();
|
|
602
|
-
if (!target || !aidType)
|
|
603
|
-
return resultErr(codes.INVALID_AID_FORMAT, 'bindAid requires aid and type');
|
|
604
|
-
try {
|
|
605
|
-
this._registerFlow.validateAidName(target);
|
|
606
|
-
const gatewayUrl = await this._resolveGateway(target);
|
|
607
|
-
const prepareParams = { aid: target, type: aidType };
|
|
608
|
-
if (owner)
|
|
609
|
-
prepareParams.owner = owner;
|
|
610
|
-
const idempotencyKey = params.idempotencyKey ?? params.idempotency_key;
|
|
611
|
-
if (idempotencyKey)
|
|
612
|
-
prepareParams.idempotency_key = idempotencyKey;
|
|
613
|
-
const prepared = await this._registerFlow.shortRpc(gatewayUrl, 'auth.aid_bind_prepare', prepareParams);
|
|
614
|
-
const operationId = String(prepared.operation_id ?? '').trim();
|
|
615
|
-
if (!operationId)
|
|
616
|
-
throw new ValidationError('aid_bind_prepare response missing operation_id');
|
|
617
|
-
const signatures = {};
|
|
618
|
-
let subjectSignature = params.subjectSignature ?? params.subject_signature;
|
|
619
|
-
if (!subjectSignature && owner) {
|
|
620
|
-
const loaded = await this.load(target);
|
|
621
|
-
if (loaded.ok) {
|
|
622
|
-
const statement = OwnershipStatement.create(String(prepared.proof_id ?? ''), target, owner);
|
|
623
|
-
const signed = await signOwnershipStatement(loaded.data.aid, statement);
|
|
624
|
-
subjectSignature = { cert_fingerprint: signed.cert_fingerprint, signature: signed.signature };
|
|
625
|
-
}
|
|
626
|
-
}
|
|
627
|
-
if (subjectSignature)
|
|
628
|
-
signatures.subject_signature = subjectSignature;
|
|
629
|
-
const ownerSignature = params.ownerSignature ?? params.owner_signature;
|
|
630
|
-
if (ownerSignature)
|
|
631
|
-
signatures.owner_signature = ownerSignature;
|
|
632
|
-
let response = prepared;
|
|
633
|
-
if (Object.keys(signatures).length > 0) {
|
|
634
|
-
response = await this._registerFlow.shortRpc(gatewayUrl, 'auth.aid_bind_submit', { operation_id: operationId, ...signatures });
|
|
635
|
-
}
|
|
636
|
-
const agentMd = params.agentMd ?? params.agent_md;
|
|
637
|
-
if (agentMd !== undefined) {
|
|
638
|
-
const uploaded = await this.uploadAgentMd(target, agentMd, {
|
|
639
|
-
ownerOperationId: operationId,
|
|
640
|
-
});
|
|
641
|
-
if (!uploaded.ok)
|
|
642
|
-
return uploaded;
|
|
643
|
-
}
|
|
644
|
-
const state = String(response.state ?? response.operation_state ?? '').toLowerCase();
|
|
645
|
-
if (state === 'agent_md_pending' || (!Object.keys(signatures).length && !owner && String(prepared.state ?? '') === 'agent_md_pending')) {
|
|
646
|
-
response = await this._registerFlow.shortRpc(gatewayUrl, 'auth.aid_bind_activate', { operation_id: operationId });
|
|
647
|
-
}
|
|
648
|
-
return resultOk(response);
|
|
649
|
-
}
|
|
650
|
-
catch (exc) {
|
|
651
|
-
if (exc instanceof ValidationError)
|
|
652
|
-
return resultErr(codes.INVALID_AID_FORMAT, exc.message, exc);
|
|
653
|
-
return resultErr(codes.NETWORK_ERROR, exc instanceof Error ? exc.message : String(exc), exc);
|
|
654
|
-
}
|
|
655
|
-
}
|
|
656
|
-
async getAidBindingStatus(aid) {
|
|
657
|
-
const target = String(aid ?? '').trim();
|
|
658
|
-
if (!target)
|
|
659
|
-
return resultErr(codes.INVALID_AID_FORMAT, 'getAidBindingStatus requires aid');
|
|
660
|
-
try {
|
|
661
|
-
this._registerFlow.validateAidName(target);
|
|
662
|
-
const gatewayUrl = await this._resolveGateway(target);
|
|
663
|
-
return resultOk(await this._registerFlow.shortRpc(gatewayUrl, 'auth.aid_bind_status', { aid: target }));
|
|
664
|
-
}
|
|
665
|
-
catch (exc) {
|
|
666
|
-
return resultErr(codes.NETWORK_ERROR, exc instanceof Error ? exc.message : String(exc), exc);
|
|
667
|
-
}
|
|
668
|
-
}
|
|
669
|
-
async resumeAidBinding(operationId) {
|
|
670
|
-
const target = String(operationId ?? '').trim();
|
|
671
|
-
if (!target)
|
|
672
|
-
return resultErr(codes.INVALID_AID_FORMAT, 'resumeAidBinding requires operationId');
|
|
673
|
-
try {
|
|
674
|
-
const gatewayUrl = await this._resolveGateway(target);
|
|
675
|
-
let response = await this._registerFlow.shortRpc(gatewayUrl, 'auth.aid_operation_get', { operation_id: target });
|
|
676
|
-
if (String(response.state ?? response.operation_state ?? '').toLowerCase() === 'agent_md_pending') {
|
|
677
|
-
const method = String(response.kind ?? '').toLowerCase() === 'owner_change' ? 'auth.owner_change_activate' : 'auth.aid_bind_activate';
|
|
678
|
-
response = await this._registerFlow.shortRpc(gatewayUrl, method, { operation_id: target });
|
|
679
|
-
}
|
|
680
|
-
return resultOk(response);
|
|
681
|
-
}
|
|
682
|
-
catch (exc) {
|
|
683
|
-
return resultErr(codes.NETWORK_ERROR, exc instanceof Error ? exc.message : String(exc), exc);
|
|
684
|
-
}
|
|
685
|
-
}
|
|
686
|
-
async bind_aid(params) { return this.bindAid(params); }
|
|
687
|
-
async get_aid_binding_status(aid) { return this.getAidBindingStatus(aid); }
|
|
688
|
-
async resume_aid_binding(operationId) { return this.resumeAidBinding(operationId); }
|
|
689
|
-
async changeOwner(params) {
|
|
690
|
-
const subject = String(params?.subject_aid ?? params?.aid ?? '').trim();
|
|
691
|
-
const newOwner = String(params?.new_owner_aid ?? params?.new_owner ?? '').trim();
|
|
692
|
-
if (!subject || !newOwner)
|
|
693
|
-
return resultErr(codes.INVALID_AID_FORMAT, 'changeOwner requires subject_aid and new_owner_aid');
|
|
694
|
-
try {
|
|
695
|
-
this._registerFlow.validateAidName(subject);
|
|
696
|
-
const gatewayUrl = await this._resolveGateway(subject);
|
|
697
|
-
const prepare = { subject_aid: subject, new_owner_aid: newOwner };
|
|
698
|
-
if (params.current_owner_aid ?? params.owner_aid)
|
|
699
|
-
prepare.current_owner_aid = params.current_owner_aid ?? params.owner_aid;
|
|
700
|
-
if (params.expected_relation_version !== undefined)
|
|
701
|
-
prepare.expected_relation_version = params.expected_relation_version;
|
|
702
|
-
if (params.idempotencyKey ?? params.idempotency_key)
|
|
703
|
-
prepare.idempotency_key = params.idempotencyKey ?? params.idempotency_key;
|
|
704
|
-
const prepared = await this._registerFlow.shortRpc(gatewayUrl, 'auth.owner_change_prepare', prepare);
|
|
705
|
-
const operationId = String(prepared.operation_id ?? '').trim();
|
|
706
|
-
if (!operationId)
|
|
707
|
-
throw new ValidationError('owner_change_prepare response missing operation_id');
|
|
708
|
-
const signatures = {};
|
|
709
|
-
for (const key of ['old_owner_signature', 'subject_signature', 'new_owner_signature'])
|
|
710
|
-
if (params[key])
|
|
711
|
-
signatures[key] = params[key];
|
|
712
|
-
let response = prepared;
|
|
713
|
-
if (Object.keys(signatures).length > 0)
|
|
714
|
-
response = await this._registerFlow.shortRpc(gatewayUrl, 'auth.owner_change_submit', { operation_id: operationId, ...signatures });
|
|
715
|
-
if (String(response.state ?? response.operation_state ?? '').toLowerCase() === 'agent_md_pending' && (params.agentMd ?? params.agent_md) !== undefined) {
|
|
716
|
-
const proof = { proof_id: prepared.proof_id, subject_aid: subject, owner_aid: newOwner, fact_hash: prepared.fact_hash, statement_hash: prepared.statement_hash, subject_signature: params.subject_signature, owner_signature: params.new_owner_signature };
|
|
717
|
-
const content = ownerAgentMd(String(params.agentMd ?? params.agent_md), newOwner, proof);
|
|
718
|
-
const uploaded = await this.uploadAgentMd(subject, content, { ownerOperationId: operationId });
|
|
719
|
-
if (!uploaded.ok)
|
|
720
|
-
return uploaded;
|
|
721
|
-
response = await this._registerFlow.shortRpc(gatewayUrl, 'auth.owner_change_activate', { operation_id: operationId });
|
|
722
|
-
}
|
|
723
|
-
return resultOk(response);
|
|
724
|
-
}
|
|
725
|
-
catch (exc) {
|
|
726
|
-
return resultErr(codes.NETWORK_ERROR, exc instanceof Error ? exc.message : String(exc), exc);
|
|
727
|
-
}
|
|
728
|
-
}
|
|
729
|
-
async resumeOwnerChange(operationId, aid) {
|
|
730
|
-
const id = String(operationId ?? '').trim();
|
|
731
|
-
if (!id)
|
|
732
|
-
return resultErr(codes.INVALID_AID_FORMAT, 'resumeOwnerChange requires operationId');
|
|
733
|
-
try {
|
|
734
|
-
const gatewayUrl = await this._resolveGateway(String(aid ?? id));
|
|
735
|
-
let response = await this._registerFlow.shortRpc(gatewayUrl, 'auth.aid_operation_get', { operation_id: id });
|
|
736
|
-
if (String(response.state ?? response.operation_state ?? '').toLowerCase() === 'agent_md_pending')
|
|
737
|
-
response = await this._registerFlow.shortRpc(gatewayUrl, 'auth.owner_change_activate', { operation_id: id });
|
|
738
|
-
return resultOk(response);
|
|
739
|
-
}
|
|
740
|
-
catch (exc) {
|
|
741
|
-
return resultErr(codes.NETWORK_ERROR, exc instanceof Error ? exc.message : String(exc), exc);
|
|
742
|
-
}
|
|
743
|
-
}
|
|
744
638
|
async exists(aid) {
|
|
745
639
|
const target = String(aid ?? '').trim();
|
|
746
640
|
try {
|
|
@@ -766,7 +660,12 @@ export class AIDStore {
|
|
|
766
660
|
let peer;
|
|
767
661
|
let certFromCache = false;
|
|
768
662
|
const cached = await this.load(target);
|
|
769
|
-
|
|
663
|
+
const localIdentity = !cached.ok && cached.error.code === codes.CERT_EXPIRED && !forceRefresh && !skipAgentMd ? await this._expiredIdentityForAgentMdRead(target) : null;
|
|
664
|
+
if (localIdentity) {
|
|
665
|
+
peer = localIdentity;
|
|
666
|
+
certFromCache = true;
|
|
667
|
+
}
|
|
668
|
+
else if (cached.ok && !forceRefresh) {
|
|
770
669
|
peer = cached.data.aid;
|
|
771
670
|
certFromCache = true;
|
|
772
671
|
}
|
|
@@ -786,6 +685,15 @@ export class AIDStore {
|
|
|
786
685
|
const source = { cert_from_cache: certFromCache, agent_md_fetched: false };
|
|
787
686
|
if (skipAgentMd)
|
|
788
687
|
return resultOk({ aid: peer, source });
|
|
688
|
+
if (!forceRefresh && (localIdentity || peer.isPrivateKeyValid() && peer.privateKeyPem)) {
|
|
689
|
+
const record = await this._agentMdManager.loadRecord(target, true);
|
|
690
|
+
const draft = String(record?.local_draft_content ?? '');
|
|
691
|
+
const local = draft || await this._agentMdManager.readContent(target);
|
|
692
|
+
if (local) {
|
|
693
|
+
const etag = await AgentMdManager.contentEtag(local);
|
|
694
|
+
return resultOk({ aid: peer, source, agent_md: { aid: target, content: local, verification: { status: 'unverified' }, signature: { status: 'unverified' }, cert_pem: peer.certPem, etag: draft ? '' : etag, last_modified: String(record?.last_modified ?? ''), status: 0, in_sync: draft ? false : etag === record?.remote_etag, saved_to: null, save_error: null } });
|
|
695
|
+
}
|
|
696
|
+
}
|
|
789
697
|
const agentMd = await this.downloadAgentMd(target, opts?.timeout);
|
|
790
698
|
if (!agentMd.ok)
|
|
791
699
|
return agentMd;
|
|
@@ -796,6 +704,25 @@ export class AIDStore {
|
|
|
796
704
|
return resultErr(codes.NETWORK_ERROR, String(exc), exc);
|
|
797
705
|
}
|
|
798
706
|
}
|
|
707
|
+
async _expiredIdentityForAgentMdRead(target) {
|
|
708
|
+
const record = await this._agentMdManager.loadRecord(target, true);
|
|
709
|
+
if (!String(record?.local_draft_content ?? '') && !await this._agentMdManager.readContent(target))
|
|
710
|
+
return null;
|
|
711
|
+
const certPem = await this._keystore.loadCert(target);
|
|
712
|
+
const keyPair = await this._keystore.loadKeyPair(target);
|
|
713
|
+
if (!certPem || !keyPair?.private_key_pem || parseCertCN(certPem) !== target)
|
|
714
|
+
return null;
|
|
715
|
+
try {
|
|
716
|
+
const privateKey = await importPrivateKeyEcdsa(String(keyPair.private_key_pem));
|
|
717
|
+
const probe = new TextEncoder().encode('aun-aidstore-private-key-self-test');
|
|
718
|
+
if (!await ecdsaVerifyDer(await importCertPublicKeyEcdsa(certPem), await ecdsaSignDer(privateKey, probe), probe))
|
|
719
|
+
return null;
|
|
720
|
+
return await AID.create({ aid: target, aunPath: this.aunPath, certPem, privateKeyPem: null, certValid: false, privateKeyValid: false, deviceId: this.deviceId, slotId: this.slotId, verifySsl: this._verifySsl, aidStore: this });
|
|
721
|
+
}
|
|
722
|
+
catch {
|
|
723
|
+
return null;
|
|
724
|
+
}
|
|
725
|
+
}
|
|
799
726
|
async downloadAgentMd(aid, timeoutMs = 30000) {
|
|
800
727
|
try {
|
|
801
728
|
return resultOk(await this._agentMdManager.download(aid, timeoutMs));
|
|
@@ -812,12 +739,30 @@ export class AIDStore {
|
|
|
812
739
|
return agentMdFailure(exc);
|
|
813
740
|
}
|
|
814
741
|
}
|
|
815
|
-
async _verifyDownloadedPeerCertificate(gatewayUrl, aid, certPem, certFingerprint = '') {
|
|
742
|
+
async _verifyDownloadedPeerCertificate(gatewayUrl, aid, certPem, certFingerprint = '', signedAt) {
|
|
816
743
|
const expectedFp = String(certFingerprint ?? '').trim().toLowerCase();
|
|
817
744
|
if (expectedFp && !(await certMatchesFingerprint(certPem, expectedFp))) {
|
|
818
745
|
throw new ValidationError(`certificate fingerprint mismatch for aid: ${aid}`);
|
|
819
746
|
}
|
|
820
|
-
|
|
747
|
+
if (signedAt === undefined)
|
|
748
|
+
await this._peerAuth.verifyPeerCertificate(gatewayUrl, certPem, aid);
|
|
749
|
+
else
|
|
750
|
+
await this._peerAuth.verifyPeerCertificate(gatewayUrl, certPem, aid, signedAt, false, true);
|
|
751
|
+
}
|
|
752
|
+
async _resolveEntityAgentMdOwner(aid, type) {
|
|
753
|
+
const authority = await this._agentMdManager.readEntityAuthorityFresh(aid);
|
|
754
|
+
const certificate = await this._currentEntityCertificate(aid, type);
|
|
755
|
+
if (authority.entity_cert_fingerprint !== certificate.certFingerprint)
|
|
756
|
+
throw new StateError('entity certificate authority changed', { stringCode: 'conflict' });
|
|
757
|
+
return authority.owner_aid;
|
|
758
|
+
}
|
|
759
|
+
async _currentEntityCertificate(aid, type) {
|
|
760
|
+
const cert = await this._peerAuth.resolveCurrentEntityCertificate(await this._resolveGateway(aid), aid, type);
|
|
761
|
+
return AID.create({ aid, aunPath: this.aunPath, certPem: cert.cert_pem, certValid: true, privateKeyPem: null, privateKeyValid: false });
|
|
762
|
+
}
|
|
763
|
+
async _currentEntitySigner(aid, fingerprint) {
|
|
764
|
+
const cert = await this._peerAuth.resolveOwnershipCertificate(await this._resolveGateway(aid), aid, fingerprint);
|
|
765
|
+
return AID.create({ aid, aunPath: this.aunPath, certPem: cert.cert_pem, certValid: true, privateKeyPem: null, privateKeyValid: false });
|
|
821
766
|
}
|
|
822
767
|
_authIdentityFromAid(aid) {
|
|
823
768
|
return {
|
|
@@ -853,10 +798,18 @@ export class AIDStore {
|
|
|
853
798
|
return resultErr(loaded.error.code, loaded.error.message);
|
|
854
799
|
}
|
|
855
800
|
const current = loaded.data.aid;
|
|
856
|
-
|
|
801
|
+
const raw = content ?? (String((await this._agentMdManager.loadRecord(target, true))?.local_draft_content ?? '') || await this._agentMdManager.readContent(target));
|
|
802
|
+
const tail = parseAgentMdTailSignatureForRead(String(raw ?? ''));
|
|
803
|
+
if (tail.parseError)
|
|
804
|
+
throw new ValidationError(tail.parseError);
|
|
805
|
+
const base = validateAgentMdDocument(tail.payload, { expectedAid: target, validateOwnership: false });
|
|
806
|
+
const entity = ['group', 'org'].includes(String(base.frontmatter.type));
|
|
807
|
+
const parsed = entity ? validateAgentMdDocument(String(raw ?? ''), { expectedAid: target }) : base;
|
|
808
|
+
const meta = parsed.frontmatter;
|
|
809
|
+
if ((!current.isPrivateKeyValid() || !current.privateKeyPem) && !(entity && parsed.signature)) {
|
|
857
810
|
return resultErr(codes.PRIVATE_KEY_NOT_VALID, `uploadAgentMd requires local AID with a valid private key: ${target}`);
|
|
858
811
|
}
|
|
859
|
-
const uploaderTarget = String(options.uploaderAid ?? options.uploader_aid ?? target).trim();
|
|
812
|
+
const uploaderTarget = String(options.uploaderAid ?? options.uploader_aid ?? (entity ? meta.owner : target)).trim();
|
|
860
813
|
this._registerFlow.validateAidName(uploaderTarget);
|
|
861
814
|
let uploader = current;
|
|
862
815
|
if (uploaderTarget.toLowerCase() !== target.toLowerCase()) {
|
|
@@ -865,22 +818,35 @@ export class AIDStore {
|
|
|
865
818
|
return resultErr(uploaderLoaded.error.code, uploaderLoaded.error.message);
|
|
866
819
|
}
|
|
867
820
|
uploader = uploaderLoaded.data.aid;
|
|
868
|
-
|
|
869
|
-
|
|
870
|
-
}
|
|
821
|
+
}
|
|
822
|
+
if (!uploader.isPrivateKeyValid() || !uploader.privateKeyPem) {
|
|
823
|
+
return resultErr(codes.PRIVATE_KEY_NOT_VALID, `uploadAgentMd requires uploader AID with a valid private key: ${uploaderTarget}`);
|
|
871
824
|
}
|
|
872
825
|
const manager = new AgentMdManager({
|
|
873
826
|
aunPath: this.aunPath,
|
|
874
827
|
tokenStore: this._tokenStore,
|
|
875
828
|
uploadViaGatewayHost: true,
|
|
876
|
-
ownerAidGetter: () =>
|
|
877
|
-
currentAidGetter: () =>
|
|
829
|
+
ownerAidGetter: () => uploaderTarget,
|
|
830
|
+
currentAidGetter: () => uploader,
|
|
831
|
+
entityOwnerResolver: options.entityOwnerResolver ?? ((value, type) => this._resolveEntityAgentMdOwner(value, type)),
|
|
832
|
+
entityIdentityGetter: options.entityIdentityGetter ?? ((value, type) => this._currentEntityCertificate(value, type ?? 'group')),
|
|
833
|
+
entitySignerResolver: options.entitySignerResolver ?? ((value, fingerprint) => this._currentEntitySigner(value, fingerprint)),
|
|
878
834
|
gatewayResolver: (value) => this._resolveGateway(value),
|
|
879
835
|
accessTokenResolver: async () => this._uploadAgentMdToken(uploader, await this._resolveGateway(uploaderTarget)),
|
|
880
836
|
aidValidator: (value) => this._registerFlow.validateAidName(value),
|
|
881
837
|
});
|
|
882
|
-
|
|
883
|
-
|
|
838
|
+
if (entity) {
|
|
839
|
+
let signed = String(raw);
|
|
840
|
+
if (!parsed.signature)
|
|
841
|
+
signed = (await signDocument(current, signed, { signerAid: true })).signed;
|
|
842
|
+
if (options.coSignerAid) {
|
|
843
|
+
if (options.coSignerAid !== meta.owner)
|
|
844
|
+
throw new ValidationError('entity additional signer must be current owner');
|
|
845
|
+
signed = (await signDocument(uploader, signed, { append: true })).signed;
|
|
846
|
+
}
|
|
847
|
+
return resultOk(await manager.uploadAs(target, signed, current));
|
|
848
|
+
}
|
|
849
|
+
return resultOk(await manager.upload(raw));
|
|
884
850
|
}
|
|
885
851
|
catch (exc) {
|
|
886
852
|
return agentMdFailure(exc);
|
|
@@ -977,7 +943,45 @@ export class AIDStore {
|
|
|
977
943
|
return resultErr(codes.PRIVATE_KEY_REQUIRED, `private key required for aid: ${target}`);
|
|
978
944
|
}
|
|
979
945
|
const gatewayUrl = await this._resolveGateway(target);
|
|
980
|
-
const
|
|
946
|
+
const pendingStore = this._keystore;
|
|
947
|
+
let pendingDir = null;
|
|
948
|
+
let newIdentity = null;
|
|
949
|
+
for (const handle of await pendingStore.listPendingIdentityDirs(target)) {
|
|
950
|
+
const candidate = await pendingStore.loadPendingKeyPair(handle, target);
|
|
951
|
+
if (candidate?.rekey_pending === true) {
|
|
952
|
+
pendingDir = handle;
|
|
953
|
+
newIdentity = candidate;
|
|
954
|
+
break;
|
|
955
|
+
}
|
|
956
|
+
}
|
|
957
|
+
if (newIdentity) {
|
|
958
|
+
const serverCert = await this._registerFlow.fetchPeerCert(gatewayUrl, target);
|
|
959
|
+
if (serverCert) {
|
|
960
|
+
const validity = parseCertValidity(serverCert);
|
|
961
|
+
const now = Date.now();
|
|
962
|
+
if (publicKeyDerB64(serverCert) === newIdentity.public_key_der_b64
|
|
963
|
+
&& parseCertCN(serverCert) === target
|
|
964
|
+
&& validity && now >= validity.notBefore && now <= validity.notAfter) {
|
|
965
|
+
await pendingStore.savePendingCert(pendingDir, serverCert);
|
|
966
|
+
await this._keystore.saveIdentity(target, {
|
|
967
|
+
aid: target, private_key_pem: newIdentity.private_key_pem,
|
|
968
|
+
public_key_der_b64: newIdentity.public_key_der_b64, curve: newIdentity.curve, cert: serverCert,
|
|
969
|
+
});
|
|
970
|
+
await pendingStore.discardPendingIdentity(pendingDir);
|
|
971
|
+
const refreshed = await this.load(target);
|
|
972
|
+
if (!refreshed.ok || !refreshed.data)
|
|
973
|
+
return resultErr(codes.REKEY_FAILED, 'rekeyed identity reload failed');
|
|
974
|
+
return resultOk({ rekeyed: true, new_cert_not_after: refreshed.data.aid.certNotAfter, new_fingerprint: refreshed.data.aid.certFingerprint });
|
|
975
|
+
}
|
|
976
|
+
}
|
|
977
|
+
}
|
|
978
|
+
else {
|
|
979
|
+
newIdentity = await this._registerFlow.generateIdentity();
|
|
980
|
+
pendingDir = await pendingStore.pendingIdentityDir(target);
|
|
981
|
+
await pendingStore.savePendingKeyPair(pendingDir, target, { ...newIdentity, aid: target, rekey_pending: true });
|
|
982
|
+
}
|
|
983
|
+
if (!pendingDir || !newIdentity)
|
|
984
|
+
return resultErr(codes.REKEY_FAILED, 'pending rekey identity is unavailable');
|
|
981
985
|
const phase1 = await this._beginAidOperation(gatewayUrl, oldAid);
|
|
982
986
|
const signPayload = `${String(phase1.nonce ?? '')}${newIdentity.public_key_der_b64}`;
|
|
983
987
|
const signResult = await loaded.data.aid.sign(signPayload);
|
|
@@ -993,6 +997,7 @@ export class AIDStore {
|
|
|
993
997
|
const certPem = String(response.cert ?? response.cert_pem ?? '').trim();
|
|
994
998
|
if (!certPem)
|
|
995
999
|
return resultErr(codes.REKEY_FAILED, 'server response missing certificate');
|
|
1000
|
+
await pendingStore.savePendingCert(pendingDir, certPem);
|
|
996
1001
|
const nextIdentity = {
|
|
997
1002
|
aid: target,
|
|
998
1003
|
private_key_pem: newIdentity.private_key_pem,
|
|
@@ -1001,6 +1006,7 @@ export class AIDStore {
|
|
|
1001
1006
|
cert: certPem,
|
|
1002
1007
|
};
|
|
1003
1008
|
await this._keystore.saveIdentity(target, nextIdentity);
|
|
1009
|
+
await pendingStore.discardPendingIdentity(pendingDir);
|
|
1004
1010
|
const refreshed = await this.load(target);
|
|
1005
1011
|
if (!refreshed.ok)
|
|
1006
1012
|
return resultErr(codes.REKEY_FAILED, 'rekeyed identity reload failed');
|