@agentunion/fastaun 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 +15 -1
- package/dist/agent-md-schema.js +87 -24
- package/dist/agent-md-schema.js.map +1 -1
- package/dist/agent-md.d.ts +54 -2
- package/dist/agent-md.js +637 -53
- package/dist/agent-md.js.map +1 -1
- package/dist/aid-store.d.ts +38 -59
- package/dist/aid-store.js +300 -265
- package/dist/aid-store.js.map +1 -1
- package/dist/aid.js +30 -11
- package/dist/aid.js.map +1 -1
- package/dist/auth.d.ts +6 -1
- package/dist/auth.js +500 -15
- package/dist/auth.js.map +1 -1
- package/dist/cert-utils.d.ts +44 -2
- package/dist/cert-utils.js +149 -49
- package/dist/cert-utils.js.map +1 -1
- package/dist/client/delivery.d.ts +11 -1
- package/dist/client/delivery.js +321 -62
- package/dist/client/delivery.js.map +1 -1
- package/dist/client/rpc-pipeline.d.ts +1 -0
- package/dist/client/rpc-pipeline.js +142 -82
- package/dist/client/rpc-pipeline.js.map +1 -1
- package/dist/client/v2-e2ee.d.ts +1 -0
- package/dist/client/v2-e2ee.js +166 -96
- package/dist/client/v2-e2ee.js.map +1 -1
- package/dist/client.d.ts +84 -1
- package/dist/client.js +780 -30
- 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 +9 -3
- package/dist/events.js +69 -11
- package/dist/events.js.map +1 -1
- package/dist/facades.d.ts +83 -3
- package/dist/facades.js +100 -6
- package/dist/facades.js.map +1 -1
- package/dist/index.d.ts +9 -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 +57 -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 +1753 -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 +196 -0
- package/dist/ownership-v21.js +712 -0
- package/dist/ownership-v21.js.map +1 -0
- package/dist/ownership-workflow.d.ts +87 -0
- package/dist/ownership-workflow.js +1182 -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 +90 -1
- package/dist/tools/cross-sdk-agent.js +441 -7
- package/dist/tools/cross-sdk-agent.js.map +1 -1
- package/dist/transport.d.ts +8 -0
- package/dist/transport.js +118 -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,14 @@
|
|
|
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
|
+
import { readFileSync, writeFileSync } from 'node:fs';
|
|
10
11
|
import { AID } from './aid.js';
|
|
11
12
|
import * as codes from './error-codes.js';
|
|
12
|
-
import { certCommonName, certMatchesFingerprint, certTimeError, signBytes, verifySignatureWithCert } from './cert-utils.js';
|
|
13
|
+
import { certCommonName, certMatchesFingerprint, certTimeError, parseAgentMdTailSignature, publicKeyMatchesFingerprint, signBytes, signDocument, verifyDocument, verifySignatureWithCert } from './cert-utils.js';
|
|
14
|
+
import { certFingerprint as _certFingerprint } from './cert-utils.js';
|
|
13
15
|
import { resultErr, resultOk } from './result.js';
|
|
14
16
|
import { LocalIdentityStore } from './keystore/local-identity-store.js';
|
|
15
17
|
import { CryptoProvider } from './crypto.js';
|
|
@@ -18,17 +20,21 @@ import { GatewayDiscovery } from './discovery.js';
|
|
|
18
20
|
import { DnsResilientNet } from './net.js';
|
|
19
21
|
import { AUNLogger } from './logger.js';
|
|
20
22
|
import { getDeviceId, normalizeSlotId } from './config.js';
|
|
21
|
-
import { ClientSignatureError, IdentityConflictError, NotFoundError, ValidationError } from './errors.js';
|
|
23
|
+
import { AUNError, ClientSignatureError, IdentityConflictError, NotFoundError, ValidationError } from './errors.js';
|
|
22
24
|
import { AgentMdManager, buildDefaultAgentMd } from './agent-md.js';
|
|
25
|
+
import { parseFileFrontmatter, validateAgentMdCertificate, validateAgentMdDocument } from './agent-md-schema.js';
|
|
23
26
|
import { LocalTokenStore } from './keystore/local-token-store.js';
|
|
24
27
|
import { AuthFlow } from './auth.js';
|
|
25
|
-
import {
|
|
28
|
+
import { OWNERSHIP_UUID_V4_RE, ownershipFailure, signOwnershipFile } from './ownership-v21.js';
|
|
29
|
+
import { validateAIDFormat } from './validators.js';
|
|
26
30
|
function resultError(r) {
|
|
27
31
|
if (r.ok)
|
|
28
32
|
return null;
|
|
29
33
|
return r.error;
|
|
30
34
|
}
|
|
31
35
|
function agentMdFailure(exc) {
|
|
36
|
+
if (exc instanceof AUNError && exc.stringCode)
|
|
37
|
+
return resultErr(exc.stringCode, exc.message, exc);
|
|
32
38
|
if (exc instanceof ClientSignatureError) {
|
|
33
39
|
return resultErr(codes.SIGNATURE_INVALID, exc.message, exc);
|
|
34
40
|
}
|
|
@@ -40,19 +46,6 @@ function agentMdFailure(exc) {
|
|
|
40
46
|
}
|
|
41
47
|
return resultErr(codes.NETWORK_ERROR, exc instanceof Error ? exc.message : String(exc), exc);
|
|
42
48
|
}
|
|
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
49
|
/** 发起 HTTP HEAD 请求,返回 [status, headers] */
|
|
57
50
|
function httpHead(url, verifySsl, timeoutMs = 5000) {
|
|
58
51
|
return new Promise((resolve, reject) => {
|
|
@@ -104,6 +97,114 @@ export class AIDStore {
|
|
|
104
97
|
_gatewayCache = new Map();
|
|
105
98
|
_agentMdManager;
|
|
106
99
|
_peerAuth;
|
|
100
|
+
async sign(aid, input, options = {}) {
|
|
101
|
+
try {
|
|
102
|
+
if (!['text', 'file'].includes(options.inputType ?? 'text') || !['raw', 'document'].includes(options.format ?? 'raw'))
|
|
103
|
+
throw new Error('invalid signature inputType or format');
|
|
104
|
+
if (options.append && options.format !== 'document')
|
|
105
|
+
throw new Error('append requires document format');
|
|
106
|
+
if (options.inPlace && (options.inputType !== 'file' || options.format !== 'document'))
|
|
107
|
+
throw new Error('inPlace requires file input and document format');
|
|
108
|
+
const raw = options.inputType === 'file' ? readFileSync(input) : input;
|
|
109
|
+
if (typeof raw === 'string' && options.format !== 'document' && new TextDecoder('utf-8', { fatal: true, ignoreBOM: true }).decode(new TextEncoder().encode(raw)) !== raw)
|
|
110
|
+
throw new Error('signature text must be valid UTF-8');
|
|
111
|
+
const loaded = options.identity ? resultOk({ aid: options.identity }) : this.load(aid);
|
|
112
|
+
if (!loaded.ok)
|
|
113
|
+
return loaded;
|
|
114
|
+
const identity = loaded.data.aid;
|
|
115
|
+
if (identity.aid !== aid || identity.certSubject.toLowerCase() !== aid.toLowerCase() || !identity.isPrivateKeyValid() || !identity.isCertValid())
|
|
116
|
+
return resultErr(codes.PRIVATE_KEY_NOT_VALID, 'sign requires a valid local signing identity');
|
|
117
|
+
if (options.format !== 'document')
|
|
118
|
+
return identity.sign(raw);
|
|
119
|
+
const content = typeof raw === 'string' ? raw : new TextDecoder('utf-8', { fatal: true, ignoreBOM: true }).decode(raw);
|
|
120
|
+
const signed = signDocument(identity, content, { append: options.append, tag: options.tag, timestamp: options.timestamp, signerAid: options.signerAid });
|
|
121
|
+
if (options.inPlace)
|
|
122
|
+
writeFileSync(input, signed.signed, 'utf8');
|
|
123
|
+
return resultOk(signed);
|
|
124
|
+
}
|
|
125
|
+
catch (error) {
|
|
126
|
+
return resultErr(codes.SIGNATURE_OPERATION_ERROR, error instanceof Error ? error.message : String(error), error);
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
async verify(aid, input, signatureOrOptions, options = {}) {
|
|
130
|
+
const signature = typeof signatureOrOptions === 'string' ? signatureOrOptions : undefined;
|
|
131
|
+
if (signatureOrOptions && typeof signatureOrOptions === 'object')
|
|
132
|
+
options = signatureOrOptions;
|
|
133
|
+
try {
|
|
134
|
+
if (!['text', 'file'].includes(options.inputType ?? 'text') || !['raw', 'document'].includes(options.format ?? 'raw'))
|
|
135
|
+
throw new Error('invalid signature inputType or format');
|
|
136
|
+
if (options.inPlace)
|
|
137
|
+
throw new Error('verify does not modify files');
|
|
138
|
+
if (options.identity && (options.identity.aid !== aid || options.identity.certSubject.toLowerCase() !== aid.toLowerCase()))
|
|
139
|
+
throw new Error('verification identity does not match AID');
|
|
140
|
+
const raw = options.inputType === 'file' ? readFileSync(input) : input;
|
|
141
|
+
if (typeof raw === 'string' && options.format !== 'document' && new TextDecoder('utf-8', { fatal: true, ignoreBOM: true }).decode(new TextEncoder().encode(raw)) !== raw)
|
|
142
|
+
throw new Error('signature text must be valid UTF-8');
|
|
143
|
+
if (options.format === 'document') {
|
|
144
|
+
const text = typeof raw === 'string' ? raw : new TextDecoder('utf-8', { fatal: true, ignoreBOM: true }).decode(raw);
|
|
145
|
+
const parsed = parseAgentMdTailSignature(text, options.readCompatibility);
|
|
146
|
+
if (parsed.parseError || !parsed.fields)
|
|
147
|
+
return resultOk(verifyDocument(text, { expectedAid: aid, readCompatibility: options.readCompatibility }));
|
|
148
|
+
}
|
|
149
|
+
let loaded = options.identity ? resultOk({ aid: options.identity }) : this.load(aid);
|
|
150
|
+
if (!loaded.ok && options.format === 'document' && options.certificateResolver) {
|
|
151
|
+
const text = typeof raw === 'string' ? raw : new TextDecoder('utf-8', { fatal: true, ignoreBOM: true }).decode(raw);
|
|
152
|
+
const primary = parseAgentMdTailSignature(text, options.readCompatibility).fields;
|
|
153
|
+
if (primary) {
|
|
154
|
+
const certificate = await options.certificateResolver(aid, primary.cert_fingerprint, Number(primary.timestamp));
|
|
155
|
+
loaded = resultOk({ aid: typeof certificate === 'string' ? AID._create({ aid, aunPath: this.aunPath, certPem: certificate, certValid: true, privateKeyPem: null, privateKeyValid: false }) : certificate });
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
if (!loaded.ok)
|
|
159
|
+
return loaded;
|
|
160
|
+
const identity = loaded.data.aid;
|
|
161
|
+
if (identity.aid !== aid || identity.certSubject.toLowerCase() !== aid.toLowerCase())
|
|
162
|
+
throw new Error('verification identity does not match AID');
|
|
163
|
+
if (options.format !== 'document')
|
|
164
|
+
return identity.verify(raw, signature ?? '');
|
|
165
|
+
const content = typeof raw === 'string' ? raw : new TextDecoder('utf-8', { fatal: true, ignoreBOM: true }).decode(raw);
|
|
166
|
+
const certificates = await this._documentCertificates(content, options, identity);
|
|
167
|
+
const primary = parseAgentMdTailSignature(content, options.readCompatibility).fields;
|
|
168
|
+
const primaryCertPem = primary ? certificates.get(`${aid};${primary.cert_fingerprint}`) ?? identity.certPem : identity.certPem;
|
|
169
|
+
return resultOk(verifyDocument(content, { expectedAid: aid, primaryCertPem, readCompatibility: options.readCompatibility, requireCurrentValidity: options.requireCurrentValidity, certificateResolver: (signer, fingerprint) => certificates.get(`${signer};${fingerprint}`) }));
|
|
170
|
+
}
|
|
171
|
+
catch (error) {
|
|
172
|
+
return resultErr(codes.VERIFICATION_OPERATION_ERROR, error instanceof Error ? error.message : String(error), error);
|
|
173
|
+
}
|
|
174
|
+
}
|
|
175
|
+
async _documentCertificates(content, options, primary) {
|
|
176
|
+
const certificates = new Map();
|
|
177
|
+
let inferredAid = primary?.aid;
|
|
178
|
+
if (!inferredAid) {
|
|
179
|
+
try {
|
|
180
|
+
const meta = parseFileFrontmatter(parseAgentMdTailSignature(content).payload);
|
|
181
|
+
inferredAid = String(meta.aid ?? meta.owner_aid ?? '') || undefined;
|
|
182
|
+
}
|
|
183
|
+
catch { }
|
|
184
|
+
}
|
|
185
|
+
for (const item of parseAgentMdTailSignature(content, options.readCompatibility).signatures) {
|
|
186
|
+
const fields = item.fields, signer = fields.signer_aid ?? inferredAid;
|
|
187
|
+
if (!signer)
|
|
188
|
+
continue;
|
|
189
|
+
let cert;
|
|
190
|
+
if (options.certificateResolver)
|
|
191
|
+
cert = await options.certificateResolver(signer, fields.cert_fingerprint, Number(fields.timestamp));
|
|
192
|
+
else if (primary?.aid === signer && certMatchesFingerprint(primary.certPem, fields.cert_fingerprint))
|
|
193
|
+
cert = primary;
|
|
194
|
+
else {
|
|
195
|
+
const loaded = this.load(signer);
|
|
196
|
+
if (loaded.ok && certMatchesFingerprint(loaded.data.aid.certPem, fields.cert_fingerprint))
|
|
197
|
+
cert = loaded.data.aid;
|
|
198
|
+
else
|
|
199
|
+
throw new Error('signing certificate is unavailable locally; an explicit certificateResolver is required');
|
|
200
|
+
}
|
|
201
|
+
if (cert && typeof cert !== 'string' && cert.aid !== signer)
|
|
202
|
+
throw new Error('resolved certificate identity does not match signer AID');
|
|
203
|
+
if (cert)
|
|
204
|
+
certificates.set(`${signer};${fields.cert_fingerprint}`, typeof cert === 'string' ? cert : cert.certPem);
|
|
205
|
+
}
|
|
206
|
+
return certificates;
|
|
207
|
+
}
|
|
107
208
|
constructor(opts) {
|
|
108
209
|
this.aunPath = String(opts.aunPath ?? join(homedir(), '.aun'));
|
|
109
210
|
this._encryptionSeed = String(opts.encryptionSeed ?? '');
|
|
@@ -148,28 +249,15 @@ export class AIDStore {
|
|
|
148
249
|
logger: this._log.for('aun_core.agent_md'),
|
|
149
250
|
aidValidator: (target) => this._registerFlow.validateAidName(target),
|
|
150
251
|
gatewayResolver: (target) => this._resolveGateway(target),
|
|
151
|
-
|
|
152
|
-
const
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
const gatewayUrl = await this._resolveGateway(target);
|
|
159
|
-
const certPem = await this._net.httpGetText(pkiCertUrl(gatewayUrl, target, fp), 10_000);
|
|
160
|
-
await this._verifyDownloadedPeerCertificate(gatewayUrl, target, certPem, fp);
|
|
161
|
-
this._keystore.saveCert(target, certPem, fp, { makeActive: false });
|
|
162
|
-
return AID._create({ aid: target, aunPath: this.aunPath, certPem, privateKeyPem: null, certValid: true, privateKeyValid: false });
|
|
163
|
-
}
|
|
164
|
-
const gatewayUrl = await this._resolveGateway(target);
|
|
165
|
-
const certPem = await this._registerFlow.fetchPeerCert(gatewayUrl, target);
|
|
166
|
-
if (!certPem) {
|
|
167
|
-
throw new Error(`certificate not found for aid: ${target}`);
|
|
168
|
-
}
|
|
169
|
-
await this._verifyDownloadedPeerCertificate(gatewayUrl, target, certPem);
|
|
170
|
-
this._keystore.saveCert(target, certPem);
|
|
171
|
-
return AID._create({ aid: target, aunPath: this.aunPath, certPem, privateKeyPem: null, certValid: true, privateKeyValid: false });
|
|
252
|
+
entitySignerResolver: async (target, fingerprint) => {
|
|
253
|
+
const material = await this._peerAuth.resolveOwnershipCertificate(await this._resolveGateway(target), target, fingerprint, 'signing');
|
|
254
|
+
return AID._create({ aid: target, aunPath: this.aunPath, certPem: material.cert_pem, privateKeyPem: null, certValid: true, privateKeyValid: false });
|
|
255
|
+
},
|
|
256
|
+
entityIdentityGetter: async (target, type) => {
|
|
257
|
+
const material = await this._peerAuth.resolveCurrentEntityCertificate(await this._resolveGateway(target), target, type ?? 'org');
|
|
258
|
+
return AID._create({ aid: target, aunPath: this.aunPath, certPem: material.cert_pem, privateKeyPem: null, certValid: true, privateKeyValid: false });
|
|
172
259
|
},
|
|
260
|
+
peerResolver: (target, certFingerprint, signedAt) => this._resolveAgentMdPeerCertificate(target, certFingerprint, signedAt),
|
|
173
261
|
});
|
|
174
262
|
}
|
|
175
263
|
close() {
|
|
@@ -179,6 +267,29 @@ export class AIDStore {
|
|
|
179
267
|
if (typeof net.close === 'function')
|
|
180
268
|
net.close();
|
|
181
269
|
}
|
|
270
|
+
async _resolveAgentMdPeerCertificate(target, certFingerprint, signedAt) {
|
|
271
|
+
const fp = String(certFingerprint ?? '').trim().toLowerCase();
|
|
272
|
+
const cachedCert = this._keystore.loadCert(target, fp || undefined);
|
|
273
|
+
if (cachedCert && (!fp || certMatchesFingerprint(cachedCert, fp) || publicKeyMatchesFingerprint(cachedCert, fp))) {
|
|
274
|
+
if (signedAt !== undefined)
|
|
275
|
+
await this._verifyDownloadedPeerCertificate(await this._resolveGateway(target), target, cachedCert, fp, signedAt);
|
|
276
|
+
return AID._create({ aid: target, aunPath: this.aunPath, certPem: cachedCert, privateKeyPem: null, certValid: true, privateKeyValid: false, deviceId: this.deviceId, slotId: this.slotId, verifySsl: this._verifySsl, rootCaPath: this._rootCaPath, debug: this._debug });
|
|
277
|
+
}
|
|
278
|
+
if (fp) {
|
|
279
|
+
const gatewayUrl = await this._resolveGateway(target);
|
|
280
|
+
const certPem = await this._net.httpGetText(pkiCertUrl(gatewayUrl, target, fp), 10_000);
|
|
281
|
+
await this._verifyDownloadedPeerCertificate(gatewayUrl, target, certPem, fp, signedAt);
|
|
282
|
+
this._keystore.saveCert(target, certPem, _certFingerprint(certPem), { makeActive: false });
|
|
283
|
+
return AID._create({ aid: target, aunPath: this.aunPath, certPem, privateKeyPem: null, certValid: true, privateKeyValid: false });
|
|
284
|
+
}
|
|
285
|
+
const gatewayUrl = await this._resolveGateway(target);
|
|
286
|
+
const certPem = await this._registerFlow.fetchPeerCert(gatewayUrl, target);
|
|
287
|
+
if (!certPem)
|
|
288
|
+
throw new Error(`certificate not found for aid: ${target}`);
|
|
289
|
+
await this._verifyDownloadedPeerCertificate(gatewayUrl, target, certPem, undefined, signedAt);
|
|
290
|
+
this._keystore.saveCert(target, certPem);
|
|
291
|
+
return AID._create({ aid: target, aunPath: this.aunPath, certPem, privateKeyPem: null, certValid: true, privateKeyValid: false });
|
|
292
|
+
}
|
|
182
293
|
// ── 离线方法 ──────────────────────────────────────────────────
|
|
183
294
|
load(aid) {
|
|
184
295
|
const target = String(aid ?? '').trim();
|
|
@@ -193,6 +304,9 @@ export class AIDStore {
|
|
|
193
304
|
catch (exc) {
|
|
194
305
|
return resultErr(codes.CERT_PARSE_ERROR, `certificate parse failed for aid: ${target}`, exc);
|
|
195
306
|
}
|
|
307
|
+
if (this._tokenStore.loadMetadata?.(target)?.invalidated_entity_certificate === _certFingerprint(certPem)) {
|
|
308
|
+
return resultErr(codes.CERT_NOT_VALID, `group/org identity has been invalidated: ${target}`);
|
|
309
|
+
}
|
|
196
310
|
const timeErr = certTimeError(certPem);
|
|
197
311
|
if (timeErr === 'expired')
|
|
198
312
|
return resultErr(codes.CERT_EXPIRED, `certificate expired for aid: ${target}`);
|
|
@@ -295,11 +409,28 @@ export class AIDStore {
|
|
|
295
409
|
return Promise.resolve(this.changeSeed(oldSeed, newSeed));
|
|
296
410
|
}
|
|
297
411
|
// ── 联网方法 ──────────────────────────────────────────────────
|
|
298
|
-
async
|
|
412
|
+
async register(aid, options) {
|
|
299
413
|
const target = String(aid ?? '').trim();
|
|
300
414
|
this._log.for('aun_core.aid_store').debug(`register enter: aid=${target || '-'}`);
|
|
301
415
|
try {
|
|
302
416
|
this._registerFlow.validateAidName(target);
|
|
417
|
+
const type = String(options?.type ?? '').trim(), owner = String(options?.owner ?? '').trim().toLowerCase();
|
|
418
|
+
const entity = ['group', 'org'].includes(type);
|
|
419
|
+
if (entity && !owner)
|
|
420
|
+
throw new ValidationError('group/org registration requires owner');
|
|
421
|
+
if (!type || type === 'human' && owner)
|
|
422
|
+
throw new ValidationError('register requires type; human forbids owner');
|
|
423
|
+
if (owner)
|
|
424
|
+
validateAIDFormat(owner, 'owner');
|
|
425
|
+
const previous = this._tokenStore.loadMetadata(target)?.registration_intent;
|
|
426
|
+
const exttype = String(options?.exttype ?? '').trim();
|
|
427
|
+
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'))
|
|
428
|
+
throw ownershipFailure('persistence_unavailable', 'registration ownership intent is corrupt');
|
|
429
|
+
if (previous && (previous.type !== type || previous.owner !== owner || previous.exttype !== exttype))
|
|
430
|
+
throw new IdentityConflictError('registration ownership intent already exists with a different context');
|
|
431
|
+
const intent = previous ?? (entity ? { protocol: 'entity', type, owner, exttype, pending: false } : { protocol: '2.1', type, owner, exttype, operation_id: randomUUID(), pending: Boolean(owner) });
|
|
432
|
+
if (type !== 'agent' || owner)
|
|
433
|
+
this._tokenStore.saveMetadata(target, { registration_intent: intent });
|
|
303
434
|
const gatewayUrl = await this._resolveGateway(target);
|
|
304
435
|
const result = await this._registerFlow.registerAid(gatewayUrl, target);
|
|
305
436
|
if (result.cert) {
|
|
@@ -310,8 +441,8 @@ export class AIDStore {
|
|
|
310
441
|
public_key_der_b64: result.public_key_der_b64,
|
|
311
442
|
curve: result.curve,
|
|
312
443
|
});
|
|
313
|
-
if (
|
|
314
|
-
await this.
|
|
444
|
+
if (!entity)
|
|
445
|
+
await this._createInitialRegistrationAgentMd(target, type, exttype);
|
|
315
446
|
return resultOk({ registered: true });
|
|
316
447
|
}
|
|
317
448
|
catch (exc) {
|
|
@@ -328,219 +459,32 @@ export class AIDStore {
|
|
|
328
459
|
return resultErr(codes.SERVER_ERROR, msg, exc);
|
|
329
460
|
}
|
|
330
461
|
}
|
|
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');
|
|
462
|
+
async _createInitialRegistrationAgentMd(target, type, exttype) {
|
|
403
463
|
try {
|
|
404
|
-
this.
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
if (
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
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');
|
|
475
|
-
try {
|
|
476
|
-
const gatewayUrl = await this._resolveGateway(target);
|
|
477
|
-
let response = await this._registerFlow.shortRpc(gatewayUrl, 'auth.aid_operation_get', { operation_id: target });
|
|
478
|
-
if (String(response.state ?? response.operation_state ?? '').toLowerCase() === 'agent_md_pending') {
|
|
479
|
-
const method = String(response.kind ?? '').toLowerCase() === 'owner_change' ? 'auth.owner_change_activate' : 'auth.aid_bind_activate';
|
|
480
|
-
response = await this._registerFlow.shortRpc(gatewayUrl, method, { operation_id: target });
|
|
481
|
-
}
|
|
482
|
-
return resultOk(response);
|
|
483
|
-
}
|
|
484
|
-
catch (exc) {
|
|
485
|
-
return resultErr(codes.NETWORK_ERROR, exc instanceof Error ? exc.message : String(exc), exc);
|
|
486
|
-
}
|
|
487
|
-
}
|
|
488
|
-
async bind_aid(params) { return this.bindAid(params); }
|
|
489
|
-
async get_aid_binding_status(aid) { return this.getAidBindingStatus(aid); }
|
|
490
|
-
async resume_aid_binding(operationId) { return this.resumeAidBinding(operationId); }
|
|
491
|
-
async changeOwner(params) {
|
|
492
|
-
const subject = String(params?.subject_aid ?? params?.aid ?? '').trim();
|
|
493
|
-
const newOwner = String(params?.new_owner_aid ?? params?.new_owner ?? '').trim();
|
|
494
|
-
if (!subject || !newOwner)
|
|
495
|
-
return resultErr(codes.INVALID_AID_FORMAT, 'changeOwner requires subject_aid and new_owner_aid');
|
|
496
|
-
try {
|
|
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);
|
|
464
|
+
const loaded = this.load(target);
|
|
465
|
+
if (!loaded.ok)
|
|
466
|
+
return loaded;
|
|
467
|
+
const current = loaded.data.aid;
|
|
468
|
+
const manager = new AgentMdManager({
|
|
469
|
+
aunPath: this.aunPath, verifySsl: this._verifySsl, logger: this._log.for('aun_core.agent_md'), uploadViaGatewayHost: true,
|
|
470
|
+
ownerAidGetter: () => target, currentAidGetter: () => current,
|
|
471
|
+
gatewayResolver: (value) => this._resolveGateway(value),
|
|
472
|
+
accessTokenResolver: async () => this._uploadAgentMdToken(current, await this._resolveGateway(target)),
|
|
473
|
+
aidValidator: (value) => this._registerFlow.validateAidName(value),
|
|
474
|
+
});
|
|
475
|
+
const fresh = await manager.readOwnershipFresh(target), now = Math.trunc(Date.now() / 1000);
|
|
476
|
+
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)
|
|
477
|
+
throw ownershipFailure('unavailable', 'initial NS authority is unavailable', true);
|
|
478
|
+
if (fresh.exists)
|
|
479
|
+
return resultOk({ initialized: false });
|
|
480
|
+
if (fresh.relation_version !== 0 || fresh.lock !== 'unlocked')
|
|
481
|
+
throw ownershipFailure('conflict', 'initial NS relation is not unbound');
|
|
482
|
+
const raw = signOwnershipFile(current, buildDefaultAgentMd(target, { type, exttype }), 'agent', { now });
|
|
483
|
+
await manager.submitOwnershipRaw(target, raw, { if_none_match: '*' });
|
|
484
|
+
return resultOk({ initialized: true });
|
|
541
485
|
}
|
|
542
|
-
catch (
|
|
543
|
-
return
|
|
486
|
+
catch (error) {
|
|
487
|
+
return agentMdFailure(error);
|
|
544
488
|
}
|
|
545
489
|
}
|
|
546
490
|
async exists(aid) {
|
|
@@ -569,6 +513,19 @@ export class AIDStore {
|
|
|
569
513
|
let peer;
|
|
570
514
|
let certFromCache = false;
|
|
571
515
|
const loaded = this.load(target);
|
|
516
|
+
const local = !forceRefresh && !skipAgentMd && ((loaded.ok && loaded.data?.aid.isPrivateKeyValid()) || this._keystore.loadKeyPair(target)?.private_key_pem) ? (this._agentMdManager._loadRecord?.(target, true) ?? this._agentMdManager.loadRecord(target)) : null;
|
|
517
|
+
const draft = String(local?.local_draft_content ?? '');
|
|
518
|
+
const content = draft || String(local?.content ?? '');
|
|
519
|
+
if (content) {
|
|
520
|
+
peer = loaded.ok && loaded.data ? loaded.data.aid : AID._create({ aid: target, aunPath: this.aunPath, certPem: this._keystore.loadCert(target) ?? '', privateKeyPem: null, certValid: false, privateKeyValid: false, deviceId: this.deviceId, slotId: this.slotId, verifySsl: this._verifySsl, rootCaPath: this._rootCaPath, debug: this._debug, aidStore: this });
|
|
521
|
+
const localEtag = AgentMdManager.contentEtag(content), remoteEtag = String(local?.remote_etag ?? '');
|
|
522
|
+
return resultOk({ aid: peer, source: { cert_from_cache: true, agent_md_fetched: false }, agent_md: {
|
|
523
|
+
aid: target, content, verification: { status: 'unverified' }, signature: {}, cert_pem: '',
|
|
524
|
+
etag: localEtag, last_modified: String(local?.last_modified ?? ''), status: 200,
|
|
525
|
+
in_sync: !draft && !!remoteEtag && localEtag === remoteEtag,
|
|
526
|
+
saved_to: draft ? this._agentMdManager.metaPath(target) : this._agentMdManager.filePath(target), save_error: null,
|
|
527
|
+
} });
|
|
528
|
+
}
|
|
572
529
|
if (loaded.ok && loaded.data && !forceRefresh) {
|
|
573
530
|
peer = loaded.data.aid;
|
|
574
531
|
certFromCache = true;
|
|
@@ -595,6 +552,8 @@ export class AIDStore {
|
|
|
595
552
|
return resultOk({ aid: peer, agent_md: agentMd.data, source });
|
|
596
553
|
}
|
|
597
554
|
catch (exc) {
|
|
555
|
+
if (exc instanceof AUNError && exc.stringCode)
|
|
556
|
+
return resultErr(exc.stringCode, exc.message, exc);
|
|
598
557
|
return resultErr(codes.NETWORK_ERROR, String(exc), exc);
|
|
599
558
|
}
|
|
600
559
|
}
|
|
@@ -614,12 +573,17 @@ export class AIDStore {
|
|
|
614
573
|
return agentMdFailure(exc);
|
|
615
574
|
}
|
|
616
575
|
}
|
|
617
|
-
async _verifyDownloadedPeerCertificate(gatewayUrl, aid, certPem, certFingerprint = '') {
|
|
576
|
+
async _verifyDownloadedPeerCertificate(gatewayUrl, aid, certPem, certFingerprint = '', signedAt) {
|
|
618
577
|
const expectedFp = String(certFingerprint ?? '').trim().toLowerCase();
|
|
619
|
-
if (expectedFp && !certMatchesFingerprint(certPem, expectedFp)) {
|
|
578
|
+
if (expectedFp && !certMatchesFingerprint(certPem, expectedFp) && !(signedAt !== undefined && publicKeyMatchesFingerprint(certPem, expectedFp))) {
|
|
620
579
|
throw new ValidationError(`certificate fingerprint mismatch for aid: ${aid}`);
|
|
621
580
|
}
|
|
622
|
-
|
|
581
|
+
if (signedAt === undefined)
|
|
582
|
+
await this._peerAuth.verifyPeerCertificate(gatewayUrl, certPem, aid);
|
|
583
|
+
else {
|
|
584
|
+
validateAgentMdCertificate(certPem, aid, signedAt, { requireCurrentValidity: false, readCompatibility: true });
|
|
585
|
+
await this._peerAuth._verifyAuthCertChain(gatewayUrl, new X509Certificate(certPem), aid, signedAt);
|
|
586
|
+
}
|
|
623
587
|
}
|
|
624
588
|
_authIdentityFromAid(aid) {
|
|
625
589
|
return {
|
|
@@ -658,10 +622,11 @@ export class AIDStore {
|
|
|
658
622
|
return resultErr(err?.code ?? codes.CERT_NOT_FOUND, err?.message ?? `certificate not found for aid: ${target}`);
|
|
659
623
|
}
|
|
660
624
|
const current = loaded.data.aid;
|
|
661
|
-
|
|
662
|
-
|
|
663
|
-
|
|
664
|
-
const
|
|
625
|
+
const raw = content ?? (String((this._agentMdManager._loadRecord?.(target, true) ?? this._agentMdManager.loadRecord(target))?.local_draft_content ?? '') || this._agentMdManager.readContent(target));
|
|
626
|
+
const parsed = validateAgentMdDocument(raw, { expectedAid: target, validateOwnership: false });
|
|
627
|
+
const meta = parsed.frontmatter;
|
|
628
|
+
const entity = ['group', 'org'].includes(String(meta.type));
|
|
629
|
+
const uploaderTarget = String(options.uploaderAid ?? options.uploader_aid ?? (entity ? meta.owner : target)).trim();
|
|
665
630
|
this._registerFlow.validateAidName(uploaderTarget);
|
|
666
631
|
let uploader = current;
|
|
667
632
|
if (uploaderTarget.toLowerCase() !== target.toLowerCase()) {
|
|
@@ -675,19 +640,48 @@ export class AIDStore {
|
|
|
675
640
|
return resultErr(codes.PRIVATE_KEY_NOT_VALID, `uploadAgentMd requires uploader AID with a valid private key: ${uploaderTarget}`);
|
|
676
641
|
}
|
|
677
642
|
}
|
|
643
|
+
if (!entity && (!current.isPrivateKeyValid() || !current.privateKeyPem)) {
|
|
644
|
+
return resultErr(codes.PRIVATE_KEY_NOT_VALID, `uploadAgentMd requires local AID with a valid private key: ${target}`);
|
|
645
|
+
}
|
|
678
646
|
const manager = new AgentMdManager({
|
|
679
647
|
aunPath: this.aunPath,
|
|
680
648
|
verifySsl: this._verifySsl,
|
|
681
649
|
logger: this._log.for('aun_core.agent_md'),
|
|
682
650
|
uploadViaGatewayHost: true,
|
|
683
|
-
ownerAidGetter: () => target,
|
|
684
|
-
currentAidGetter: () => current,
|
|
651
|
+
ownerAidGetter: () => entity ? uploaderTarget : target,
|
|
652
|
+
currentAidGetter: () => entity ? uploader : current,
|
|
653
|
+
entityIdentityGetter: options.entityIdentityGetter ?? (async (value, type) => {
|
|
654
|
+
const gateway = await this._resolveGateway(value);
|
|
655
|
+
const material = await this._peerAuth.resolveCurrentEntityCertificate(gateway, value, type ?? 'org');
|
|
656
|
+
const certPem = material.cert_pem;
|
|
657
|
+
return AID._create({ aid: value, aunPath: this.aunPath, certPem, certValid: true, privateKeyPem: null, privateKeyValid: false });
|
|
658
|
+
}),
|
|
659
|
+
entitySignerResolver: async (value, fingerprint) => {
|
|
660
|
+
const material = await this._peerAuth.resolveOwnershipCertificate(await this._resolveGateway(value), value, fingerprint, 'signing');
|
|
661
|
+
return AID._create({ aid: value, aunPath: this.aunPath, certPem: material.cert_pem, certValid: true, privateKeyPem: null, privateKeyValid: false });
|
|
662
|
+
},
|
|
663
|
+
entityOwnerResolver: options.entityOwnerResolver,
|
|
664
|
+
peerResolver: (value, fingerprint, signedAt) => this._resolveAgentMdPeerCertificate(value, fingerprint, signedAt),
|
|
685
665
|
gatewayResolver: (value) => this._resolveGateway(value),
|
|
686
666
|
accessTokenResolver: async () => this._uploadAgentMdToken(uploader, await this._resolveGateway(uploaderTarget)),
|
|
687
667
|
aidValidator: (value) => this._registerFlow.validateAidName(value),
|
|
688
668
|
});
|
|
689
|
-
|
|
690
|
-
|
|
669
|
+
let prepared = raw;
|
|
670
|
+
if (entity && !parsed.signature) {
|
|
671
|
+
const signed = await this.sign(target, raw, { format: 'document', identity: current, signerAid: target });
|
|
672
|
+
if (!signed.ok)
|
|
673
|
+
return signed;
|
|
674
|
+
prepared = signed.data.signed;
|
|
675
|
+
}
|
|
676
|
+
if (entity && options.coSignerAid) {
|
|
677
|
+
if (options.coSignerAid !== uploaderTarget)
|
|
678
|
+
throw new ValidationError('entity additional signer must be its uploading owner');
|
|
679
|
+
const signed = await this.sign(options.coSignerAid, prepared, { format: 'document', append: true, identity: uploader });
|
|
680
|
+
if (!signed.ok)
|
|
681
|
+
return signed;
|
|
682
|
+
prepared = signed.data.signed;
|
|
683
|
+
}
|
|
684
|
+
return resultOk(entity ? await manager.uploadAs(target, prepared, current) : await manager.upload(raw));
|
|
691
685
|
}
|
|
692
686
|
catch (exc) {
|
|
693
687
|
return agentMdFailure(exc);
|
|
@@ -782,8 +776,44 @@ export class AIDStore {
|
|
|
782
776
|
try {
|
|
783
777
|
this._registerFlow.validateAidName(target);
|
|
784
778
|
const oldAid = loaded.data.aid;
|
|
785
|
-
const newIdentity = this._registerFlow.generateIdentity();
|
|
786
779
|
const gatewayUrl = await this._resolveGateway(target);
|
|
780
|
+
const pendingStore = this._keystore;
|
|
781
|
+
let pendingDir = null;
|
|
782
|
+
let newIdentity = null;
|
|
783
|
+
for (const handle of pendingStore.listPendingIdentityDirs(target)) {
|
|
784
|
+
const candidate = pendingStore.loadPendingKeyPair(handle, target);
|
|
785
|
+
if (candidate?.rekey_pending === true) {
|
|
786
|
+
pendingDir = handle;
|
|
787
|
+
newIdentity = candidate;
|
|
788
|
+
break;
|
|
789
|
+
}
|
|
790
|
+
}
|
|
791
|
+
if (newIdentity) {
|
|
792
|
+
const serverCert = await this._registerFlow.fetchPeerCert(gatewayUrl, target);
|
|
793
|
+
if (serverCert) {
|
|
794
|
+
const cert = new X509Certificate(serverCert);
|
|
795
|
+
const serverPublicKey = cert.publicKey.export({ type: 'spki', format: 'der' }).toString('base64');
|
|
796
|
+
if (serverPublicKey === newIdentity.public_key_der_b64 && certCommonName(serverCert) === target && !certTimeError(serverCert)) {
|
|
797
|
+
pendingStore.savePendingCert(pendingDir, serverCert);
|
|
798
|
+
this._keystore.saveIdentity(target, {
|
|
799
|
+
aid: target, private_key_pem: newIdentity.private_key_pem,
|
|
800
|
+
public_key_der_b64: newIdentity.public_key_der_b64, curve: newIdentity.curve, cert: serverCert,
|
|
801
|
+
});
|
|
802
|
+
pendingStore.discardPendingIdentity(pendingDir);
|
|
803
|
+
const refreshed = this.load(target);
|
|
804
|
+
if (!refreshed.ok || !refreshed.data)
|
|
805
|
+
return resultErr(codes.REKEY_FAILED, 'rekeyed identity reload failed');
|
|
806
|
+
return resultOk({ rekeyed: true, new_cert_not_after: refreshed.data.aid.certNotAfter, new_fingerprint: refreshed.data.aid.certFingerprint });
|
|
807
|
+
}
|
|
808
|
+
}
|
|
809
|
+
}
|
|
810
|
+
else {
|
|
811
|
+
newIdentity = this._registerFlow.generateIdentity();
|
|
812
|
+
pendingDir = pendingStore.pendingIdentityDir(target);
|
|
813
|
+
pendingStore.savePendingKeyPair(pendingDir, target, { ...newIdentity, aid: target, rekey_pending: true });
|
|
814
|
+
}
|
|
815
|
+
if (!pendingDir || !newIdentity)
|
|
816
|
+
return resultErr(codes.REKEY_FAILED, 'pending rekey identity is unavailable');
|
|
787
817
|
const clientNonce = this._registerFlow.newClientNonce();
|
|
788
818
|
const phase1 = await this._registerFlow.shortRpc(gatewayUrl, 'auth.aid_login1', {
|
|
789
819
|
aid: target, cert: oldAid.certPem, client_nonce: clientNonce,
|
|
@@ -801,7 +831,12 @@ export class AIDStore {
|
|
|
801
831
|
const certPem = String(response.cert ?? response.cert_pem ?? '').trim();
|
|
802
832
|
if (!certPem)
|
|
803
833
|
return resultErr(codes.REKEY_FAILED, 'server response missing certificate');
|
|
804
|
-
|
|
834
|
+
pendingStore.savePendingCert(pendingDir, certPem);
|
|
835
|
+
this._keystore.saveIdentity(target, {
|
|
836
|
+
aid: target, private_key_pem: newIdentity.private_key_pem,
|
|
837
|
+
public_key_der_b64: newIdentity.public_key_der_b64, curve: newIdentity.curve, cert: certPem,
|
|
838
|
+
});
|
|
839
|
+
pendingStore.discardPendingIdentity(pendingDir);
|
|
805
840
|
const refreshed = this.load(target);
|
|
806
841
|
if (!refreshed.ok || !refreshed.data)
|
|
807
842
|
return resultErr(codes.REKEY_FAILED, 'rekeyed identity reload failed');
|