@agentunion/fastaun 0.5.18 → 0.5.19
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 +44 -2
- package/_packed_docs/CHANGELOG.md +44 -2
- package/_packed_docs/INDEX.md +136 -14
- package/_packed_docs/KITE_DOCS_GUIDE.md +42 -16
- package/_packed_docs/agent.md/SCHEMA.md +245 -205
- 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/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 +28 -9
- package/_packed_docs/protocol/11-Storage-/345/255/220/345/215/217/350/256/256.md +22 -27
- package/_packed_docs/protocol/agent.md/SCHEMA.md +4 -0
- package/_packed_docs/sdk/04-/350/277/236/346/216/245/344/270/216/350/256/244/350/257/201.md +5 -2
- package/_packed_docs/sdk/06-API/346/211/213/345/206/214.md +250 -283
- package/_packed_docs/sdk/09-group-rpc-manual.md +46 -14
- package/_packed_docs/sdk/09-message-rpc-manual.md +40 -2
- package/_packed_docs/sdk/09-storage-rpc-manual.md +74 -64
- package/_packed_docs/sdk/AUN_DOCS_GUIDE.md +25 -5
- package/_packed_docs/sdk/INDEX.md +76 -17
- package/dist/agent-md-schema.d.ts +2 -0
- package/dist/agent-md-schema.js +45 -10
- package/dist/agent-md-schema.js.map +1 -1
- package/dist/agent-md.d.ts +47 -2
- package/dist/agent-md.js +493 -33
- package/dist/agent-md.js.map +1 -1
- package/dist/aid-store.d.ts +9 -59
- package/dist/aid-store.js +135 -245
- package/dist/aid-store.js.map +1 -1
- package/dist/aid.js +19 -5
- package/dist/aid.js.map +1 -1
- package/dist/auth.d.ts +6 -1
- package/dist/auth.js +428 -15
- package/dist/auth.js.map +1 -1
- package/dist/cert-utils.d.ts +1 -1
- package/dist/cert-utils.js +19 -7
- package/dist/cert-utils.js.map +1 -1
- package/dist/client/delivery.d.ts +2 -1
- package/dist/client/delivery.js +143 -52
- package/dist/client/delivery.js.map +1 -1
- package/dist/client/rpc-pipeline.js +22 -5
- package/dist/client/rpc-pipeline.js.map +1 -1
- package/dist/client/v2-e2ee.d.ts +1 -0
- package/dist/client/v2-e2ee.js +93 -29
- package/dist/client/v2-e2ee.js.map +1 -1
- package/dist/client.d.ts +84 -1
- package/dist/client.js +749 -28
- package/dist/client.js.map +1 -1
- package/dist/errors.js +4 -3
- package/dist/errors.js.map +1 -1
- package/dist/events.d.ts +5 -1
- package/dist/events.js +47 -6
- package/dist/events.js.map +1 -1
- package/dist/facades.d.ts +83 -3
- package/dist/facades.js +98 -4
- package/dist/facades.js.map +1 -1
- package/dist/index.d.ts +8 -4
- package/dist/index.js +3 -2
- package/dist/index.js.map +1 -1
- package/dist/keystore/aid-db.d.ts +5 -0
- package/dist/keystore/aid-db.js +48 -0
- package/dist/keystore/aid-db.js.map +1 -1
- package/dist/keystore/index.d.ts +8 -0
- package/dist/keystore/local-identity-store.js +10 -3
- package/dist/keystore/local-identity-store.js.map +1 -1
- package/dist/keystore/local-token-store.d.ts +6 -1
- package/dist/keystore/local-token-store.js +24 -1
- package/dist/keystore/local-token-store.js.map +1 -1
- package/dist/org-protocol.d.ts +98 -0
- package/dist/org-protocol.js +1689 -0
- package/dist/org-protocol.js.map +1 -0
- package/dist/ownership-proof.d.ts +82 -5
- package/dist/ownership-proof.js +327 -31
- package/dist/ownership-proof.js.map +1 -1
- package/dist/ownership-v21.d.ts +178 -0
- package/dist/ownership-v21.js +635 -0
- package/dist/ownership-v21.js.map +1 -0
- package/dist/ownership-workflow.d.ts +82 -0
- package/dist/ownership-workflow.js +872 -0
- package/dist/ownership-workflow.js.map +1 -0
- package/dist/seq-tracker.d.ts +1 -0
- package/dist/seq-tracker.js +10 -1
- package/dist/seq-tracker.js.map +1 -1
- package/dist/tools/cross-sdk-agent.d.ts +89 -1
- package/dist/tools/cross-sdk-agent.js +338 -7
- package/dist/tools/cross-sdk-agent.js.map +1 -1
- package/dist/transport.d.ts +7 -0
- package/dist/transport.js +108 -4
- package/dist/transport.js.map +1 -1
- package/dist/v2/e2ee/decrypt.d.ts +1 -0
- 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
|
@@ -4,12 +4,13 @@
|
|
|
4
4
|
*/
|
|
5
5
|
import * as http from 'node:http';
|
|
6
6
|
import * as https from 'node:https';
|
|
7
|
-
import { X509Certificate, createPrivateKey, createPublicKey } from 'node:crypto';
|
|
7
|
+
import { X509Certificate, createPrivateKey, createPublicKey, randomUUID } from 'node:crypto';
|
|
8
8
|
import { join } from 'node:path';
|
|
9
9
|
import { homedir } from 'node:os';
|
|
10
10
|
import { AID } from './aid.js';
|
|
11
11
|
import * as codes from './error-codes.js';
|
|
12
12
|
import { certCommonName, certMatchesFingerprint, certTimeError, signBytes, verifySignatureWithCert } from './cert-utils.js';
|
|
13
|
+
import { certFingerprint as _certFingerprint } from './cert-utils.js';
|
|
13
14
|
import { resultErr, resultOk } from './result.js';
|
|
14
15
|
import { LocalIdentityStore } from './keystore/local-identity-store.js';
|
|
15
16
|
import { CryptoProvider } from './crypto.js';
|
|
@@ -18,17 +19,21 @@ import { GatewayDiscovery } from './discovery.js';
|
|
|
18
19
|
import { DnsResilientNet } from './net.js';
|
|
19
20
|
import { AUNLogger } from './logger.js';
|
|
20
21
|
import { getDeviceId, normalizeSlotId } from './config.js';
|
|
21
|
-
import { ClientSignatureError, IdentityConflictError, NotFoundError, ValidationError } from './errors.js';
|
|
22
|
+
import { AUNError, ClientSignatureError, IdentityConflictError, NotFoundError, ValidationError } from './errors.js';
|
|
22
23
|
import { AgentMdManager, buildDefaultAgentMd } from './agent-md.js';
|
|
24
|
+
import { validateAgentMdDocument } from './agent-md-schema.js';
|
|
23
25
|
import { LocalTokenStore } from './keystore/local-token-store.js';
|
|
24
26
|
import { AuthFlow } from './auth.js';
|
|
25
|
-
import {
|
|
27
|
+
import { OWNERSHIP_UUID_V4_RE, ownershipFailure, signOwnershipFile } from './ownership-v21.js';
|
|
28
|
+
import { validateAIDFormat } from './validators.js';
|
|
26
29
|
function resultError(r) {
|
|
27
30
|
if (r.ok)
|
|
28
31
|
return null;
|
|
29
32
|
return r.error;
|
|
30
33
|
}
|
|
31
34
|
function agentMdFailure(exc) {
|
|
35
|
+
if (exc instanceof AUNError && exc.stringCode)
|
|
36
|
+
return resultErr(exc.stringCode, exc.message, exc);
|
|
32
37
|
if (exc instanceof ClientSignatureError) {
|
|
33
38
|
return resultErr(codes.SIGNATURE_INVALID, exc.message, exc);
|
|
34
39
|
}
|
|
@@ -40,19 +45,6 @@ function agentMdFailure(exc) {
|
|
|
40
45
|
}
|
|
41
46
|
return resultErr(codes.NETWORK_ERROR, exc instanceof Error ? exc.message : String(exc), exc);
|
|
42
47
|
}
|
|
43
|
-
function ownerAgentMd(content, ownerAid, proof) {
|
|
44
|
-
const marker = content.indexOf('<!-- AUN-SIGNATURE');
|
|
45
|
-
const payload = marker >= 0 ? content.slice(0, marker) : content;
|
|
46
|
-
const lines = payload.replace(/\r\n/g, '\n').replace(/\r/g, '\n').split('\n');
|
|
47
|
-
const end = lines.findIndex((line, index) => index > 0 && line.trim() === '---');
|
|
48
|
-
if (end < 0)
|
|
49
|
-
throw new ValidationError('agent.md frontmatter is required for ownership operation');
|
|
50
|
-
const filtered = lines.filter((line, index) => !(index > 0 && index < end && (/^\s*owner\s*:/.test(line) || /^\s*ownership_proof\s*:/.test(line))));
|
|
51
|
-
const closed = filtered.findIndex((line, index) => index > 0 && line.trim() === '---');
|
|
52
|
-
filtered.splice(closed, 0, `owner: ${JSON.stringify(ownerAid)}`, `ownership_proof: ${JSON.stringify(proof)}`);
|
|
53
|
-
const result = filtered.join('\n');
|
|
54
|
-
return result.endsWith('\n') ? result : `${result}\n`;
|
|
55
|
-
}
|
|
56
48
|
/** 发起 HTTP HEAD 请求,返回 [status, headers] */
|
|
57
49
|
function httpHead(url, verifySsl, timeoutMs = 5000) {
|
|
58
50
|
return new Promise((resolve, reject) => {
|
|
@@ -148,7 +140,15 @@ export class AIDStore {
|
|
|
148
140
|
logger: this._log.for('aun_core.agent_md'),
|
|
149
141
|
aidValidator: (target) => this._registerFlow.validateAidName(target),
|
|
150
142
|
gatewayResolver: (target) => this._resolveGateway(target),
|
|
151
|
-
|
|
143
|
+
entitySignerResolver: async (target, fingerprint) => {
|
|
144
|
+
const material = await this._peerAuth.resolveOwnershipCertificate(await this._resolveGateway(target), target, fingerprint, 'signing');
|
|
145
|
+
return AID._create({ aid: target, aunPath: this.aunPath, certPem: material.cert_pem, privateKeyPem: null, certValid: true, privateKeyValid: false });
|
|
146
|
+
},
|
|
147
|
+
entityIdentityGetter: async (target, type) => {
|
|
148
|
+
const material = await this._peerAuth.resolveCurrentEntityCertificate(await this._resolveGateway(target), target, type ?? 'org');
|
|
149
|
+
return AID._create({ aid: target, aunPath: this.aunPath, certPem: material.cert_pem, privateKeyPem: null, certValid: true, privateKeyValid: false });
|
|
150
|
+
},
|
|
151
|
+
peerResolver: async (target, certFingerprint, signedAt) => {
|
|
152
152
|
const fp = String(certFingerprint ?? '').trim().toLowerCase();
|
|
153
153
|
const cachedCert = this._keystore.loadCert(target, fp || undefined);
|
|
154
154
|
if (cachedCert) {
|
|
@@ -157,7 +157,7 @@ export class AIDStore {
|
|
|
157
157
|
if (fp) {
|
|
158
158
|
const gatewayUrl = await this._resolveGateway(target);
|
|
159
159
|
const certPem = await this._net.httpGetText(pkiCertUrl(gatewayUrl, target, fp), 10_000);
|
|
160
|
-
await this._verifyDownloadedPeerCertificate(gatewayUrl, target, certPem, fp);
|
|
160
|
+
await this._verifyDownloadedPeerCertificate(gatewayUrl, target, certPem, fp, signedAt);
|
|
161
161
|
this._keystore.saveCert(target, certPem, fp, { makeActive: false });
|
|
162
162
|
return AID._create({ aid: target, aunPath: this.aunPath, certPem, privateKeyPem: null, certValid: true, privateKeyValid: false });
|
|
163
163
|
}
|
|
@@ -166,7 +166,7 @@ export class AIDStore {
|
|
|
166
166
|
if (!certPem) {
|
|
167
167
|
throw new Error(`certificate not found for aid: ${target}`);
|
|
168
168
|
}
|
|
169
|
-
await this._verifyDownloadedPeerCertificate(gatewayUrl, target, certPem);
|
|
169
|
+
await this._verifyDownloadedPeerCertificate(gatewayUrl, target, certPem, undefined, signedAt);
|
|
170
170
|
this._keystore.saveCert(target, certPem);
|
|
171
171
|
return AID._create({ aid: target, aunPath: this.aunPath, certPem, privateKeyPem: null, certValid: true, privateKeyValid: false });
|
|
172
172
|
},
|
|
@@ -193,6 +193,9 @@ export class AIDStore {
|
|
|
193
193
|
catch (exc) {
|
|
194
194
|
return resultErr(codes.CERT_PARSE_ERROR, `certificate parse failed for aid: ${target}`, exc);
|
|
195
195
|
}
|
|
196
|
+
if (this._tokenStore.loadMetadata?.(target)?.invalidated_entity_certificate === _certFingerprint(certPem)) {
|
|
197
|
+
return resultErr(codes.CERT_NOT_VALID, `group/org identity has been invalidated: ${target}`);
|
|
198
|
+
}
|
|
196
199
|
const timeErr = certTimeError(certPem);
|
|
197
200
|
if (timeErr === 'expired')
|
|
198
201
|
return resultErr(codes.CERT_EXPIRED, `certificate expired for aid: ${target}`);
|
|
@@ -295,11 +298,27 @@ export class AIDStore {
|
|
|
295
298
|
return Promise.resolve(this.changeSeed(oldSeed, newSeed));
|
|
296
299
|
}
|
|
297
300
|
// ── 联网方法 ──────────────────────────────────────────────────
|
|
298
|
-
async
|
|
301
|
+
async register(aid, options) {
|
|
299
302
|
const target = String(aid ?? '').trim();
|
|
300
303
|
this._log.for('aun_core.aid_store').debug(`register enter: aid=${target || '-'}`);
|
|
301
304
|
try {
|
|
302
305
|
this._registerFlow.validateAidName(target);
|
|
306
|
+
const type = String(options?.type ?? '').trim(), owner = String(options?.owner ?? '').trim().toLowerCase();
|
|
307
|
+
const entity = ['group', 'org'].includes(type);
|
|
308
|
+
if (entity && !owner)
|
|
309
|
+
throw new ValidationError('group/org registration requires owner');
|
|
310
|
+
if (!type || type === 'human' && owner || type === 'agent' && !owner)
|
|
311
|
+
throw new ValidationError('register requires type; human forbids owner; agent requires owner');
|
|
312
|
+
if (owner)
|
|
313
|
+
validateAIDFormat(owner, 'owner');
|
|
314
|
+
const previous = this._tokenStore.loadMetadata(target)?.registration_intent;
|
|
315
|
+
const exttype = String(options?.exttype ?? '').trim();
|
|
316
|
+
if (previous && (entity ? previous.protocol !== 'entity' || previous.pending !== false : previous.protocol !== '2.1' || !OWNERSHIP_UUID_V4_RE.test(String(previous.operation_id)) || typeof previous.pending !== 'boolean'))
|
|
317
|
+
throw ownershipFailure('persistence_unavailable', 'registration ownership intent is corrupt');
|
|
318
|
+
if (previous && (previous.type !== type || previous.owner !== owner || previous.exttype !== exttype))
|
|
319
|
+
throw new IdentityConflictError('registration ownership intent already exists with a different context');
|
|
320
|
+
const intent = previous ?? (entity ? { protocol: 'entity', type, owner, exttype, pending: false } : { protocol: '2.1', type, owner, exttype, operation_id: randomUUID(), pending: Boolean(owner) });
|
|
321
|
+
this._tokenStore.saveMetadata(target, { registration_intent: intent });
|
|
303
322
|
const gatewayUrl = await this._resolveGateway(target);
|
|
304
323
|
const result = await this._registerFlow.registerAid(gatewayUrl, target);
|
|
305
324
|
if (result.cert) {
|
|
@@ -310,8 +329,8 @@ export class AIDStore {
|
|
|
310
329
|
public_key_der_b64: result.public_key_der_b64,
|
|
311
330
|
curve: result.curve,
|
|
312
331
|
});
|
|
313
|
-
if (
|
|
314
|
-
await this.
|
|
332
|
+
if (!entity)
|
|
333
|
+
await this._createInitialRegistrationAgentMd(target, type, exttype);
|
|
315
334
|
return resultOk({ registered: true });
|
|
316
335
|
}
|
|
317
336
|
catch (exc) {
|
|
@@ -328,219 +347,32 @@ export class AIDStore {
|
|
|
328
347
|
return resultErr(codes.SERVER_ERROR, msg, exc);
|
|
329
348
|
}
|
|
330
349
|
}
|
|
331
|
-
async
|
|
332
|
-
if (typeof aidOrParams === 'string')
|
|
333
|
-
return await this.registerIdentity(aidOrParams);
|
|
334
|
-
const params = aidOrParams;
|
|
335
|
-
const target = String(params?.aid ?? '').trim();
|
|
336
|
-
const aidType = String(params?.type ?? '').trim().toLowerCase();
|
|
337
|
-
const owner = String(params?.owner ?? params?.owner_aid ?? '').trim();
|
|
338
|
-
if (!target || !aidType)
|
|
339
|
-
return resultErr(codes.INVALID_AID_FORMAT, 'register requires aid and type');
|
|
340
|
-
try {
|
|
341
|
-
const identity = await this.registerIdentity(target, false);
|
|
342
|
-
if (!identity.ok)
|
|
343
|
-
return identity;
|
|
344
|
-
const gatewayUrl = await this._resolveGateway(target);
|
|
345
|
-
const prepareParams = { aid: target, type: aidType };
|
|
346
|
-
if (owner)
|
|
347
|
-
prepareParams.owner = owner;
|
|
348
|
-
const idempotencyKey = params.idempotencyKey ?? params.idempotency_key;
|
|
349
|
-
if (idempotencyKey)
|
|
350
|
-
prepareParams.idempotency_key = idempotencyKey;
|
|
351
|
-
const prepared = await this._registerFlow.shortRpc(gatewayUrl, 'auth.aid_register_prepare', prepareParams);
|
|
352
|
-
const operationId = String(prepared.operation_id ?? '').trim();
|
|
353
|
-
if (!operationId)
|
|
354
|
-
throw new ValidationError('aid_register_prepare response missing operation_id');
|
|
355
|
-
let subjectSignature = params.subjectSignature ?? params.subject_signature;
|
|
356
|
-
const ownerSignature = params.ownerSignature ?? params.owner_signature;
|
|
357
|
-
if (!subjectSignature && owner) {
|
|
358
|
-
const loaded = this.load(target);
|
|
359
|
-
if (loaded.ok) {
|
|
360
|
-
const statement = OwnershipStatement.create(String(prepared.proof_id ?? ''), target, owner);
|
|
361
|
-
const signed = signOwnershipStatement({ certFingerprint: loaded.data.aid.certFingerprint, sign: (payload) => loaded.data.aid.sign(Buffer.from(payload)) }, statement);
|
|
362
|
-
subjectSignature = { cert_fingerprint: signed.cert_fingerprint, signature: signed.signature };
|
|
363
|
-
}
|
|
364
|
-
}
|
|
365
|
-
const signatures = {};
|
|
366
|
-
if (subjectSignature)
|
|
367
|
-
signatures.subject_signature = subjectSignature;
|
|
368
|
-
if (ownerSignature)
|
|
369
|
-
signatures.owner_signature = ownerSignature;
|
|
370
|
-
let response = prepared;
|
|
371
|
-
if (Object.keys(signatures).length > 0)
|
|
372
|
-
response = await this._registerFlow.shortRpc(gatewayUrl, 'auth.aid_register_submit', { operation_id: operationId, ...signatures });
|
|
373
|
-
const state = String(response.state ?? response.operation_state ?? '').toLowerCase();
|
|
374
|
-
const complete = !owner || owner === target || (!!ownerSignature && !!subjectSignature);
|
|
375
|
-
if (complete && state === 'agent_md_pending') {
|
|
376
|
-
let content = params.agentMd ?? params.agent_md ?? buildDefaultAgentMd(target, { type: aidType });
|
|
377
|
-
if (owner) {
|
|
378
|
-
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 };
|
|
379
|
-
if (owner !== target)
|
|
380
|
-
proof.owner_signature = ownerSignature;
|
|
381
|
-
content = ownerAgentMd(content, owner, proof);
|
|
382
|
-
}
|
|
383
|
-
const uploaded = await this.uploadAgentMd(target, content, { ownerOperationId: operationId });
|
|
384
|
-
if (!uploaded.ok)
|
|
385
|
-
return uploaded;
|
|
386
|
-
response = await this._registerFlow.shortRpc(gatewayUrl, 'auth.aid_register_activate', { operation_id: operationId });
|
|
387
|
-
}
|
|
388
|
-
return resultOk(response);
|
|
389
|
-
}
|
|
390
|
-
catch (exc) {
|
|
391
|
-
if (exc instanceof ValidationError)
|
|
392
|
-
return resultErr(codes.INVALID_AID_FORMAT, exc.message, exc);
|
|
393
|
-
return resultErr(codes.NETWORK_ERROR, exc instanceof Error ? exc.message : String(exc), exc);
|
|
394
|
-
}
|
|
395
|
-
}
|
|
396
|
-
async create(params) { return await this.register(params); }
|
|
397
|
-
async bindAid(params) {
|
|
398
|
-
const target = String(params?.aid ?? '').trim();
|
|
399
|
-
const aidType = String(params?.type ?? '').trim().toLowerCase();
|
|
400
|
-
const owner = String(params?.owner ?? params?.owner_aid ?? '').trim();
|
|
401
|
-
if (!target || !aidType)
|
|
402
|
-
return resultErr(codes.INVALID_AID_FORMAT, 'bindAid requires aid and type');
|
|
403
|
-
try {
|
|
404
|
-
this._registerFlow.validateAidName(target);
|
|
405
|
-
const gatewayUrl = await this._resolveGateway(target);
|
|
406
|
-
const prepareParams = { aid: target, type: aidType };
|
|
407
|
-
if (owner)
|
|
408
|
-
prepareParams.owner = owner;
|
|
409
|
-
const idempotencyKey = params.idempotencyKey ?? params.idempotency_key;
|
|
410
|
-
if (idempotencyKey)
|
|
411
|
-
prepareParams.idempotency_key = idempotencyKey;
|
|
412
|
-
const prepared = await this._registerFlow.shortRpc(gatewayUrl, 'auth.aid_bind_prepare', prepareParams);
|
|
413
|
-
const operationId = String(prepared.operation_id ?? '').trim();
|
|
414
|
-
if (!operationId)
|
|
415
|
-
throw new ValidationError('aid_bind_prepare response missing operation_id');
|
|
416
|
-
const signatures = {};
|
|
417
|
-
let subjectSignature = params.subjectSignature ?? params.subject_signature;
|
|
418
|
-
if (!subjectSignature && owner) {
|
|
419
|
-
const loaded = this.load(target);
|
|
420
|
-
if (loaded.ok) {
|
|
421
|
-
const statement = OwnershipStatement.create(String(prepared.proof_id ?? ''), target, owner);
|
|
422
|
-
const signed = signOwnershipStatement({
|
|
423
|
-
certFingerprint: loaded.data.aid.certFingerprint,
|
|
424
|
-
sign: (payload) => loaded.data.aid.sign(Buffer.from(payload)),
|
|
425
|
-
}, statement);
|
|
426
|
-
subjectSignature = { cert_fingerprint: signed.cert_fingerprint, signature: signed.signature };
|
|
427
|
-
}
|
|
428
|
-
}
|
|
429
|
-
if (subjectSignature)
|
|
430
|
-
signatures.subject_signature = subjectSignature;
|
|
431
|
-
const ownerSignature = params.ownerSignature ?? params.owner_signature;
|
|
432
|
-
if (ownerSignature)
|
|
433
|
-
signatures.owner_signature = ownerSignature;
|
|
434
|
-
let response = prepared;
|
|
435
|
-
if (Object.keys(signatures).length > 0) {
|
|
436
|
-
response = await this._registerFlow.shortRpc(gatewayUrl, 'auth.aid_bind_submit', { operation_id: operationId, ...signatures });
|
|
437
|
-
}
|
|
438
|
-
const agentMd = params.agentMd ?? params.agent_md;
|
|
439
|
-
if (agentMd !== undefined) {
|
|
440
|
-
const uploaded = await this.uploadAgentMd(target, agentMd, {
|
|
441
|
-
ownerOperationId: operationId,
|
|
442
|
-
});
|
|
443
|
-
if (!uploaded.ok)
|
|
444
|
-
return uploaded;
|
|
445
|
-
}
|
|
446
|
-
const state = String(response.state ?? response.operation_state ?? '').toLowerCase();
|
|
447
|
-
if (state === 'agent_md_pending' || (!Object.keys(signatures).length && !owner && String(prepared.state ?? '') === 'agent_md_pending')) {
|
|
448
|
-
response = await this._registerFlow.shortRpc(gatewayUrl, 'auth.aid_bind_activate', { operation_id: operationId });
|
|
449
|
-
}
|
|
450
|
-
return resultOk(response);
|
|
451
|
-
}
|
|
452
|
-
catch (exc) {
|
|
453
|
-
if (exc instanceof ValidationError)
|
|
454
|
-
return resultErr(codes.INVALID_AID_FORMAT, exc.message, exc);
|
|
455
|
-
return resultErr(codes.NETWORK_ERROR, exc instanceof Error ? exc.message : String(exc), exc);
|
|
456
|
-
}
|
|
457
|
-
}
|
|
458
|
-
async getAidBindingStatus(aid) {
|
|
459
|
-
const target = String(aid ?? '').trim();
|
|
460
|
-
if (!target)
|
|
461
|
-
return resultErr(codes.INVALID_AID_FORMAT, 'getAidBindingStatus requires aid');
|
|
462
|
-
try {
|
|
463
|
-
this._registerFlow.validateAidName(target);
|
|
464
|
-
const gatewayUrl = await this._resolveGateway(target);
|
|
465
|
-
return resultOk(await this._registerFlow.shortRpc(gatewayUrl, 'auth.aid_bind_status', { aid: target }));
|
|
466
|
-
}
|
|
467
|
-
catch (exc) {
|
|
468
|
-
return resultErr(codes.NETWORK_ERROR, exc instanceof Error ? exc.message : String(exc), exc);
|
|
469
|
-
}
|
|
470
|
-
}
|
|
471
|
-
async resumeAidBinding(operationId) {
|
|
472
|
-
const target = String(operationId ?? '').trim();
|
|
473
|
-
if (!target)
|
|
474
|
-
return resultErr(codes.INVALID_AID_FORMAT, 'resumeAidBinding requires operationId');
|
|
350
|
+
async _createInitialRegistrationAgentMd(target, type, exttype) {
|
|
475
351
|
try {
|
|
476
|
-
const
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
this._registerFlow.validateAidName(subject);
|
|
498
|
-
const gatewayUrl = await this._resolveGateway(subject);
|
|
499
|
-
const prepare = { subject_aid: subject, new_owner_aid: newOwner };
|
|
500
|
-
if (params.current_owner_aid ?? params.owner_aid)
|
|
501
|
-
prepare.current_owner_aid = params.current_owner_aid ?? params.owner_aid;
|
|
502
|
-
if (params.expected_relation_version !== undefined)
|
|
503
|
-
prepare.expected_relation_version = params.expected_relation_version;
|
|
504
|
-
if (params.idempotencyKey ?? params.idempotency_key)
|
|
505
|
-
prepare.idempotency_key = params.idempotencyKey ?? params.idempotency_key;
|
|
506
|
-
const prepared = await this._registerFlow.shortRpc(gatewayUrl, 'auth.owner_change_prepare', prepare);
|
|
507
|
-
const operationId = String(prepared.operation_id ?? '').trim();
|
|
508
|
-
if (!operationId)
|
|
509
|
-
throw new ValidationError('owner_change_prepare response missing operation_id');
|
|
510
|
-
const signatures = {};
|
|
511
|
-
for (const key of ['old_owner_signature', 'subject_signature', 'new_owner_signature'])
|
|
512
|
-
if (params[key])
|
|
513
|
-
signatures[key] = params[key];
|
|
514
|
-
let response = prepared;
|
|
515
|
-
if (Object.keys(signatures).length > 0)
|
|
516
|
-
response = await this._registerFlow.shortRpc(gatewayUrl, 'auth.owner_change_submit', { operation_id: operationId, ...signatures });
|
|
517
|
-
if (String(response.state ?? response.operation_state ?? '').toLowerCase() === 'agent_md_pending' && (params.agentMd ?? params.agent_md) !== undefined) {
|
|
518
|
-
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 };
|
|
519
|
-
const content = ownerAgentMd(String(params.agentMd ?? params.agent_md), newOwner, proof);
|
|
520
|
-
const uploaded = await this.uploadAgentMd(subject, content, { ownerOperationId: operationId });
|
|
521
|
-
if (!uploaded.ok)
|
|
522
|
-
return uploaded;
|
|
523
|
-
response = await this._registerFlow.shortRpc(gatewayUrl, 'auth.owner_change_activate', { operation_id: operationId });
|
|
524
|
-
}
|
|
525
|
-
return resultOk(response);
|
|
526
|
-
}
|
|
527
|
-
catch (exc) {
|
|
528
|
-
return resultErr(codes.NETWORK_ERROR, exc instanceof Error ? exc.message : String(exc), exc);
|
|
529
|
-
}
|
|
530
|
-
}
|
|
531
|
-
async resumeOwnerChange(operationId, aid) {
|
|
532
|
-
const id = String(operationId ?? '').trim();
|
|
533
|
-
if (!id)
|
|
534
|
-
return resultErr(codes.INVALID_AID_FORMAT, 'resumeOwnerChange requires operationId');
|
|
535
|
-
try {
|
|
536
|
-
const gatewayUrl = await this._resolveGateway(String(aid ?? id));
|
|
537
|
-
let response = await this._registerFlow.shortRpc(gatewayUrl, 'auth.aid_operation_get', { operation_id: id });
|
|
538
|
-
if (String(response.state ?? response.operation_state ?? '').toLowerCase() === 'agent_md_pending')
|
|
539
|
-
response = await this._registerFlow.shortRpc(gatewayUrl, 'auth.owner_change_activate', { operation_id: id });
|
|
540
|
-
return resultOk(response);
|
|
352
|
+
const loaded = this.load(target);
|
|
353
|
+
if (!loaded.ok)
|
|
354
|
+
return loaded;
|
|
355
|
+
const current = loaded.data.aid;
|
|
356
|
+
const manager = new AgentMdManager({
|
|
357
|
+
aunPath: this.aunPath, verifySsl: this._verifySsl, logger: this._log.for('aun_core.agent_md'), uploadViaGatewayHost: true,
|
|
358
|
+
ownerAidGetter: () => target, currentAidGetter: () => current,
|
|
359
|
+
gatewayResolver: (value) => this._resolveGateway(value),
|
|
360
|
+
accessTokenResolver: async () => this._uploadAgentMdToken(current, await this._resolveGateway(target)),
|
|
361
|
+
aidValidator: (value) => this._registerFlow.validateAidName(value),
|
|
362
|
+
});
|
|
363
|
+
const fresh = await manager.readOwnershipFresh(target), now = Math.trunc(Date.now() / 1000);
|
|
364
|
+
if (fresh.protocol !== '2.1' || fresh.sub_aid !== target || !Number.isSafeInteger(fresh.relation_version) || fresh.relation_version < 0 || !['locked', 'unlocked'].includes(fresh.lock) || !Number.isSafeInteger(fresh.observed_at) || fresh.observed_at < now - 30 || fresh.observed_at > now + 5)
|
|
365
|
+
throw ownershipFailure('unavailable', 'initial NS authority is unavailable', true);
|
|
366
|
+
if (fresh.exists)
|
|
367
|
+
return resultOk({ initialized: false });
|
|
368
|
+
if (fresh.relation_version !== 0 || fresh.lock !== 'unlocked')
|
|
369
|
+
throw ownershipFailure('conflict', 'initial NS relation is not unbound');
|
|
370
|
+
const raw = signOwnershipFile(current, buildDefaultAgentMd(target, { type, exttype }), 'agent', { now });
|
|
371
|
+
await manager.submitOwnershipRaw(target, raw, { if_none_match: '*' });
|
|
372
|
+
return resultOk({ initialized: true });
|
|
541
373
|
}
|
|
542
|
-
catch (
|
|
543
|
-
return
|
|
374
|
+
catch (error) {
|
|
375
|
+
return agentMdFailure(error);
|
|
544
376
|
}
|
|
545
377
|
}
|
|
546
378
|
async exists(aid) {
|
|
@@ -614,12 +446,15 @@ export class AIDStore {
|
|
|
614
446
|
return agentMdFailure(exc);
|
|
615
447
|
}
|
|
616
448
|
}
|
|
617
|
-
async _verifyDownloadedPeerCertificate(gatewayUrl, aid, certPem, certFingerprint = '') {
|
|
449
|
+
async _verifyDownloadedPeerCertificate(gatewayUrl, aid, certPem, certFingerprint = '', signedAt) {
|
|
618
450
|
const expectedFp = String(certFingerprint ?? '').trim().toLowerCase();
|
|
619
451
|
if (expectedFp && !certMatchesFingerprint(certPem, expectedFp)) {
|
|
620
452
|
throw new ValidationError(`certificate fingerprint mismatch for aid: ${aid}`);
|
|
621
453
|
}
|
|
622
|
-
|
|
454
|
+
if (signedAt === undefined)
|
|
455
|
+
await this._peerAuth.verifyPeerCertificate(gatewayUrl, certPem, aid);
|
|
456
|
+
else
|
|
457
|
+
await this._peerAuth.verifyPeerCertificate(gatewayUrl, certPem, aid, signedAt);
|
|
623
458
|
}
|
|
624
459
|
_authIdentityFromAid(aid) {
|
|
625
460
|
return {
|
|
@@ -658,10 +493,11 @@ export class AIDStore {
|
|
|
658
493
|
return resultErr(err?.code ?? codes.CERT_NOT_FOUND, err?.message ?? `certificate not found for aid: ${target}`);
|
|
659
494
|
}
|
|
660
495
|
const current = loaded.data.aid;
|
|
661
|
-
|
|
662
|
-
|
|
663
|
-
|
|
664
|
-
const
|
|
496
|
+
const raw = content ?? this._agentMdManager.readContent(target);
|
|
497
|
+
const parsed = validateAgentMdDocument(raw, { expectedAid: target });
|
|
498
|
+
const meta = parsed.frontmatter;
|
|
499
|
+
const entity = ['group', 'org'].includes(String(meta.type));
|
|
500
|
+
const uploaderTarget = String(options.uploaderAid ?? options.uploader_aid ?? (entity ? meta.owner : target)).trim();
|
|
665
501
|
this._registerFlow.validateAidName(uploaderTarget);
|
|
666
502
|
let uploader = current;
|
|
667
503
|
if (uploaderTarget.toLowerCase() !== target.toLowerCase()) {
|
|
@@ -675,19 +511,32 @@ export class AIDStore {
|
|
|
675
511
|
return resultErr(codes.PRIVATE_KEY_NOT_VALID, `uploadAgentMd requires uploader AID with a valid private key: ${uploaderTarget}`);
|
|
676
512
|
}
|
|
677
513
|
}
|
|
514
|
+
if ((!current.isPrivateKeyValid() || !current.privateKeyPem) && !(entity && parsed.signature && uploaderTarget.toLowerCase() !== target.toLowerCase())) {
|
|
515
|
+
return resultErr(codes.PRIVATE_KEY_NOT_VALID, `uploadAgentMd requires local AID with a valid private key: ${target}`);
|
|
516
|
+
}
|
|
678
517
|
const manager = new AgentMdManager({
|
|
679
518
|
aunPath: this.aunPath,
|
|
680
519
|
verifySsl: this._verifySsl,
|
|
681
520
|
logger: this._log.for('aun_core.agent_md'),
|
|
682
521
|
uploadViaGatewayHost: true,
|
|
683
|
-
ownerAidGetter: () => target,
|
|
684
|
-
currentAidGetter: () => current,
|
|
522
|
+
ownerAidGetter: () => entity ? uploaderTarget : target,
|
|
523
|
+
currentAidGetter: () => entity ? uploader : current,
|
|
524
|
+
entityIdentityGetter: options.entityIdentityGetter ?? (async (value, type) => {
|
|
525
|
+
const gateway = await this._resolveGateway(value);
|
|
526
|
+
const material = await this._peerAuth.resolveCurrentEntityCertificate(gateway, value, type ?? 'org');
|
|
527
|
+
const certPem = material.cert_pem;
|
|
528
|
+
return AID._create({ aid: value, aunPath: this.aunPath, certPem, certValid: true, privateKeyPem: null, privateKeyValid: false });
|
|
529
|
+
}),
|
|
530
|
+
entitySignerResolver: async (value, fingerprint) => {
|
|
531
|
+
const material = await this._peerAuth.resolveOwnershipCertificate(await this._resolveGateway(value), value, fingerprint, 'signing');
|
|
532
|
+
return AID._create({ aid: value, aunPath: this.aunPath, certPem: material.cert_pem, certValid: true, privateKeyPem: null, privateKeyValid: false });
|
|
533
|
+
},
|
|
534
|
+
entityOwnerResolver: options.entityOwnerResolver,
|
|
685
535
|
gatewayResolver: (value) => this._resolveGateway(value),
|
|
686
536
|
accessTokenResolver: async () => this._uploadAgentMdToken(uploader, await this._resolveGateway(uploaderTarget)),
|
|
687
537
|
aidValidator: (value) => this._registerFlow.validateAidName(value),
|
|
688
538
|
});
|
|
689
|
-
|
|
690
|
-
return resultOk(await manager.upload(content, ownerOperationId));
|
|
539
|
+
return resultOk(entity ? await manager.uploadAs(target, raw, current) : await manager.upload(raw));
|
|
691
540
|
}
|
|
692
541
|
catch (exc) {
|
|
693
542
|
return agentMdFailure(exc);
|
|
@@ -782,8 +631,44 @@ export class AIDStore {
|
|
|
782
631
|
try {
|
|
783
632
|
this._registerFlow.validateAidName(target);
|
|
784
633
|
const oldAid = loaded.data.aid;
|
|
785
|
-
const newIdentity = this._registerFlow.generateIdentity();
|
|
786
634
|
const gatewayUrl = await this._resolveGateway(target);
|
|
635
|
+
const pendingStore = this._keystore;
|
|
636
|
+
let pendingDir = null;
|
|
637
|
+
let newIdentity = null;
|
|
638
|
+
for (const handle of pendingStore.listPendingIdentityDirs(target)) {
|
|
639
|
+
const candidate = pendingStore.loadPendingKeyPair(handle, target);
|
|
640
|
+
if (candidate?.rekey_pending === true) {
|
|
641
|
+
pendingDir = handle;
|
|
642
|
+
newIdentity = candidate;
|
|
643
|
+
break;
|
|
644
|
+
}
|
|
645
|
+
}
|
|
646
|
+
if (newIdentity) {
|
|
647
|
+
const serverCert = await this._registerFlow.fetchPeerCert(gatewayUrl, target);
|
|
648
|
+
if (serverCert) {
|
|
649
|
+
const cert = new X509Certificate(serverCert);
|
|
650
|
+
const serverPublicKey = cert.publicKey.export({ type: 'spki', format: 'der' }).toString('base64');
|
|
651
|
+
if (serverPublicKey === newIdentity.public_key_der_b64 && certCommonName(serverCert) === target && !certTimeError(serverCert)) {
|
|
652
|
+
pendingStore.savePendingCert(pendingDir, serverCert);
|
|
653
|
+
this._keystore.saveIdentity(target, {
|
|
654
|
+
aid: target, private_key_pem: newIdentity.private_key_pem,
|
|
655
|
+
public_key_der_b64: newIdentity.public_key_der_b64, curve: newIdentity.curve, cert: serverCert,
|
|
656
|
+
});
|
|
657
|
+
pendingStore.discardPendingIdentity(pendingDir);
|
|
658
|
+
const refreshed = this.load(target);
|
|
659
|
+
if (!refreshed.ok || !refreshed.data)
|
|
660
|
+
return resultErr(codes.REKEY_FAILED, 'rekeyed identity reload failed');
|
|
661
|
+
return resultOk({ rekeyed: true, new_cert_not_after: refreshed.data.aid.certNotAfter, new_fingerprint: refreshed.data.aid.certFingerprint });
|
|
662
|
+
}
|
|
663
|
+
}
|
|
664
|
+
}
|
|
665
|
+
else {
|
|
666
|
+
newIdentity = this._registerFlow.generateIdentity();
|
|
667
|
+
pendingDir = pendingStore.pendingIdentityDir(target);
|
|
668
|
+
pendingStore.savePendingKeyPair(pendingDir, target, { ...newIdentity, aid: target, rekey_pending: true });
|
|
669
|
+
}
|
|
670
|
+
if (!pendingDir || !newIdentity)
|
|
671
|
+
return resultErr(codes.REKEY_FAILED, 'pending rekey identity is unavailable');
|
|
787
672
|
const clientNonce = this._registerFlow.newClientNonce();
|
|
788
673
|
const phase1 = await this._registerFlow.shortRpc(gatewayUrl, 'auth.aid_login1', {
|
|
789
674
|
aid: target, cert: oldAid.certPem, client_nonce: clientNonce,
|
|
@@ -801,7 +686,12 @@ export class AIDStore {
|
|
|
801
686
|
const certPem = String(response.cert ?? response.cert_pem ?? '').trim();
|
|
802
687
|
if (!certPem)
|
|
803
688
|
return resultErr(codes.REKEY_FAILED, 'server response missing certificate');
|
|
804
|
-
|
|
689
|
+
pendingStore.savePendingCert(pendingDir, certPem);
|
|
690
|
+
this._keystore.saveIdentity(target, {
|
|
691
|
+
aid: target, private_key_pem: newIdentity.private_key_pem,
|
|
692
|
+
public_key_der_b64: newIdentity.public_key_der_b64, curve: newIdentity.curve, cert: certPem,
|
|
693
|
+
});
|
|
694
|
+
pendingStore.discardPendingIdentity(pendingDir);
|
|
805
695
|
const refreshed = this.load(target);
|
|
806
696
|
if (!refreshed.ok || !refreshed.data)
|
|
807
697
|
return resultErr(codes.REKEY_FAILED, 'rekeyed identity reload failed');
|