@dnsid-ai/sdk 0.20.1
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/LICENSE +201 -0
- package/README.md +121 -0
- package/dist/index.cjs +657 -0
- package/dist/index.d.cts +210 -0
- package/dist/index.d.ts +210 -0
- package/dist/index.js +625 -0
- package/dist/node.cjs +560 -0
- package/dist/node.d.cts +195 -0
- package/dist/node.d.ts +195 -0
- package/dist/node.js +518 -0
- package/package.json +61 -0
package/dist/index.js
ADDED
|
@@ -0,0 +1,625 @@
|
|
|
1
|
+
// src/index.ts
|
|
2
|
+
import { IdentityManager } from "@dnsid-ai/protocol";
|
|
3
|
+
export * from "@dnsid-ai/protocol";
|
|
4
|
+
import {
|
|
5
|
+
IdentityManager as IdentityManager2,
|
|
6
|
+
JWKS_MAX_RESPONSE_BYTES,
|
|
7
|
+
STATUS_MAX_RESPONSE_BYTES,
|
|
8
|
+
isTransientVerificationError,
|
|
9
|
+
retryTransientVerification,
|
|
10
|
+
DNSID_VERSION as DNSID_VERSION2,
|
|
11
|
+
DnsIdTxtRecord,
|
|
12
|
+
JWKS as JWKS3,
|
|
13
|
+
jwkThumbprint as jwkThumbprint3,
|
|
14
|
+
jwkSignatureAlg,
|
|
15
|
+
SIGNING_ALGS,
|
|
16
|
+
VerifiedDomain,
|
|
17
|
+
DomainLog,
|
|
18
|
+
DomainSnapshot,
|
|
19
|
+
LogRegistry as LogRegistry2,
|
|
20
|
+
NoopLogReader,
|
|
21
|
+
InMemoryIdentityCache,
|
|
22
|
+
validateAgentStatus,
|
|
23
|
+
activeStatusDocument,
|
|
24
|
+
ParseError,
|
|
25
|
+
ValidationError,
|
|
26
|
+
VerificationError as VerificationError3,
|
|
27
|
+
VerificationCode as VerificationCode3,
|
|
28
|
+
ArgumentError,
|
|
29
|
+
normalizeFQDN as normalizeFQDN2,
|
|
30
|
+
isDomainName,
|
|
31
|
+
parseKeyId,
|
|
32
|
+
toBase64Url,
|
|
33
|
+
fromBase64Url,
|
|
34
|
+
toArrayBuffer,
|
|
35
|
+
parseKaDuration,
|
|
36
|
+
verifyWithKey,
|
|
37
|
+
DNSSECState,
|
|
38
|
+
DNSSECMode
|
|
39
|
+
} from "@dnsid-ai/protocol";
|
|
40
|
+
import * as jose from "@dnsid-ai/jose";
|
|
41
|
+
import * as httpSignatures from "@dnsid-ai/http-signatures";
|
|
42
|
+
import * as registry from "@dnsid-ai/registry";
|
|
43
|
+
import * as webBotAuth from "@dnsid-ai/web-bot-auth";
|
|
44
|
+
import { JoseProfile, createJoseProfile } from "@dnsid-ai/jose";
|
|
45
|
+
import { HttpSignaturesProfile, createHttpSignaturesProfile } from "@dnsid-ai/http-signatures";
|
|
46
|
+
import {
|
|
47
|
+
RegistryClient,
|
|
48
|
+
PreparedEventSubmissionError,
|
|
49
|
+
RegistryWorkflowError,
|
|
50
|
+
awaitRegistryManagedPublication,
|
|
51
|
+
publishClientControlledRecord,
|
|
52
|
+
publishToRegistry,
|
|
53
|
+
DEFAULT_REGISTRY_URL
|
|
54
|
+
} from "@dnsid-ai/registry";
|
|
55
|
+
|
|
56
|
+
// src/managed-key-rotation.ts
|
|
57
|
+
import {
|
|
58
|
+
JWKS,
|
|
59
|
+
VerificationCode,
|
|
60
|
+
VerificationError,
|
|
61
|
+
jwkThumbprint
|
|
62
|
+
} from "@dnsid-ai/protocol";
|
|
63
|
+
import {
|
|
64
|
+
c2spTlogEntryBytes,
|
|
65
|
+
parsePreparedC2spTlogEvent,
|
|
66
|
+
signPreparedC2spTlogEvent
|
|
67
|
+
} from "@dnsid-ai/log-c2sp-tlog/writer";
|
|
68
|
+
var ManagedKeyRotationSubmissionError = class extends VerificationError {
|
|
69
|
+
rotation;
|
|
70
|
+
retryWithSameBytes;
|
|
71
|
+
constructor(message, rotation, retryWithSameBytes2, cause) {
|
|
72
|
+
super(message, { code: VerificationCode.LogError, transient: retryWithSameBytes2, cause });
|
|
73
|
+
this.name = "ManagedKeyRotationSubmissionError";
|
|
74
|
+
this.rotation = rotation;
|
|
75
|
+
this.retryWithSameBytes = retryWithSameBytes2;
|
|
76
|
+
}
|
|
77
|
+
};
|
|
78
|
+
var ManagedKeyRotationActivationError = class extends VerificationError {
|
|
79
|
+
rotation;
|
|
80
|
+
constructor(message, rotation, cause) {
|
|
81
|
+
super(message, { code: VerificationCode.LogError, transient: true, cause });
|
|
82
|
+
this.name = "ManagedKeyRotationActivationError";
|
|
83
|
+
this.rotation = rotation;
|
|
84
|
+
}
|
|
85
|
+
};
|
|
86
|
+
async function rotateManagedOperationalKey(options) {
|
|
87
|
+
const previousKey = await options.keyProvider.signingKey();
|
|
88
|
+
new JWKS([previousKey]).validateOperational();
|
|
89
|
+
const previousThumbprint = await jwkThumbprint(previousKey);
|
|
90
|
+
const newKid = await options.keyProvider.generateKey();
|
|
91
|
+
const newKey = await options.keyProvider.jwk(newKid);
|
|
92
|
+
new JWKS([newKey]).validateOperational();
|
|
93
|
+
if (await jwkThumbprint(newKey) === previousThumbprint) {
|
|
94
|
+
throw permanentRotationError("managed KEY_ROTATION requires a distinct pending operational key");
|
|
95
|
+
}
|
|
96
|
+
let raw;
|
|
97
|
+
try {
|
|
98
|
+
raw = await options.registryClient.prepareKeyRotation(options.domain, {
|
|
99
|
+
previousKeyId: previousThumbprint,
|
|
100
|
+
publicKey: newKey
|
|
101
|
+
}, options.idempotencyKey);
|
|
102
|
+
} catch (cause) {
|
|
103
|
+
throw new VerificationError("managed key rotation preparation is unavailable", {
|
|
104
|
+
code: VerificationCode.LogError,
|
|
105
|
+
transient: true,
|
|
106
|
+
cause
|
|
107
|
+
});
|
|
108
|
+
}
|
|
109
|
+
if (raw.logReference !== options.logReference) {
|
|
110
|
+
throw permanentRotationError(`registry preparation returned unexpected log reference ${raw.logReference}`);
|
|
111
|
+
}
|
|
112
|
+
const context = { expectedFqdn: options.domain, previousOperationalKey: previousKey };
|
|
113
|
+
let prepared = await parsePreparedC2spTlogEvent(raw.entryBytes, raw.logReference, context);
|
|
114
|
+
if (prepared.envelope.type !== "KEY_ROTATION") {
|
|
115
|
+
throw permanentRotationError(`registry preparation returned ${String(prepared.envelope.type)} instead of KEY_ROTATION`);
|
|
116
|
+
}
|
|
117
|
+
if (prepared.envelope.fqdn !== options.domain) {
|
|
118
|
+
throw permanentRotationError("registry KEY_ROTATION fqdn does not match the expected identity");
|
|
119
|
+
}
|
|
120
|
+
if (prepared.envelope.prev_thumb !== previousThumbprint) {
|
|
121
|
+
throw permanentRotationError("registry KEY_ROTATION previous thumbprint does not match the active operational key");
|
|
122
|
+
}
|
|
123
|
+
await assertPreparedRotationKey(prepared.envelope.new_ku, prepared.envelope.new_thumb, newKey);
|
|
124
|
+
prepared = await signPreparedC2spTlogEvent(prepared, "PreviousOperational", options.keyProvider, context);
|
|
125
|
+
prepared = await signPreparedC2spTlogEvent(prepared, "NewOperational", options.keyProvider, context);
|
|
126
|
+
const entryBytes = await c2spTlogEntryBytes(prepared, context);
|
|
127
|
+
const rotation = {
|
|
128
|
+
domain: options.domain,
|
|
129
|
+
logReference: options.logReference,
|
|
130
|
+
previousKid: previousKey.kid,
|
|
131
|
+
newKid,
|
|
132
|
+
entryBytes: entryBytes.slice(),
|
|
133
|
+
idempotencyKey: options.idempotencyKey,
|
|
134
|
+
activated: false,
|
|
135
|
+
applicationSigningPaused: true
|
|
136
|
+
};
|
|
137
|
+
await options.persistRotation(rotation);
|
|
138
|
+
await options.setApplicationSigningPaused(true);
|
|
139
|
+
return submitManagedRotation(options.keyProvider, options.registryClient, rotation, options.persistRotation, options.setApplicationSigningPaused);
|
|
140
|
+
}
|
|
141
|
+
async function resumeManagedOperationalKeyRotation(options) {
|
|
142
|
+
if (options.rotation.activated) {
|
|
143
|
+
if (!options.rotation.applicationSigningPaused) return options.rotation;
|
|
144
|
+
await options.setApplicationSigningPaused(false);
|
|
145
|
+
const completed = { ...options.rotation, applicationSigningPaused: false };
|
|
146
|
+
await options.persistRotation(completed);
|
|
147
|
+
return completed;
|
|
148
|
+
}
|
|
149
|
+
if (options.rotation.submission?.state === "rejected") {
|
|
150
|
+
throw permanentRotationError("cannot resume a rejected managed key rotation");
|
|
151
|
+
}
|
|
152
|
+
await options.setApplicationSigningPaused(true);
|
|
153
|
+
return submitManagedRotation(options.keyProvider, options.registryClient, options.rotation, options.persistRotation, options.setApplicationSigningPaused);
|
|
154
|
+
}
|
|
155
|
+
async function submitManagedRotation(keyProvider, registryClient, rotation, persistRotation, setApplicationSigningPaused) {
|
|
156
|
+
let submission;
|
|
157
|
+
try {
|
|
158
|
+
submission = await registryClient.submitPreparedEvent(rotation.domain, rotation.entryBytes, rotation.idempotencyKey);
|
|
159
|
+
} catch (cause) {
|
|
160
|
+
const retryWithSameBytes2 = structuredRetryWithSameBytes(cause);
|
|
161
|
+
throw new ManagedKeyRotationSubmissionError(
|
|
162
|
+
retryWithSameBytes2 ? "managed key rotation submission is unresolved; retry the same bytes and idempotency key" : "managed key rotation submission failed terminally",
|
|
163
|
+
rotation,
|
|
164
|
+
retryWithSameBytes2,
|
|
165
|
+
cause
|
|
166
|
+
);
|
|
167
|
+
}
|
|
168
|
+
await assertSubmissionMatchesRotation(rotation, submission);
|
|
169
|
+
const next = { ...rotation, submission };
|
|
170
|
+
await persistRotation(next);
|
|
171
|
+
if (submission.state === "rejected") {
|
|
172
|
+
throw new ManagedKeyRotationSubmissionError(
|
|
173
|
+
`registry rejected the prepared KEY_ROTATION: ${submission.errorCode ?? "unknown error"}`,
|
|
174
|
+
next,
|
|
175
|
+
false
|
|
176
|
+
);
|
|
177
|
+
}
|
|
178
|
+
if (submission.state !== "accepted") return next;
|
|
179
|
+
let recoverable = next;
|
|
180
|
+
try {
|
|
181
|
+
await reconcileAcceptedRotation(keyProvider, rotation);
|
|
182
|
+
const activated = { ...next, activated: true };
|
|
183
|
+
await persistRotation(activated);
|
|
184
|
+
recoverable = activated;
|
|
185
|
+
await setApplicationSigningPaused(false);
|
|
186
|
+
const completed = { ...activated, applicationSigningPaused: false };
|
|
187
|
+
await persistRotation(completed);
|
|
188
|
+
return completed;
|
|
189
|
+
} catch (cause) {
|
|
190
|
+
throw new ManagedKeyRotationActivationError(
|
|
191
|
+
"managed key rotation was accepted, but local key activation is incomplete; resume with this rotation state",
|
|
192
|
+
recoverable,
|
|
193
|
+
cause
|
|
194
|
+
);
|
|
195
|
+
}
|
|
196
|
+
}
|
|
197
|
+
async function reconcileAcceptedRotation(keyProvider, rotation) {
|
|
198
|
+
const activeKid = (await keyProvider.signingKey()).kid;
|
|
199
|
+
if (activeKid === rotation.previousKid) {
|
|
200
|
+
await keyProvider.activate(rotation.newKid);
|
|
201
|
+
} else if (activeKid !== rotation.newKid) {
|
|
202
|
+
throw permanentRotationError(`unexpected active key ${activeKid} while reconciling managed key rotation`);
|
|
203
|
+
}
|
|
204
|
+
if ((await keyProvider.listKeyIds()).includes(rotation.previousKid)) {
|
|
205
|
+
await keyProvider.supersede(rotation.previousKid);
|
|
206
|
+
}
|
|
207
|
+
}
|
|
208
|
+
async function assertPreparedRotationKey(value, thumbprint, expected) {
|
|
209
|
+
if (!value || typeof value !== "object" || Array.isArray(value)) throw permanentRotationError("registry KEY_ROTATION is missing new_ku");
|
|
210
|
+
const actual = value;
|
|
211
|
+
const actualThumbprint = await jwkThumbprint(actual);
|
|
212
|
+
const expectedThumbprint = await jwkThumbprint(expected);
|
|
213
|
+
if (actual.kid !== expected.kid || actual.alg !== expected.alg || actualThumbprint !== expectedThumbprint) {
|
|
214
|
+
throw permanentRotationError("registry KEY_ROTATION new key does not match the generated pending key");
|
|
215
|
+
}
|
|
216
|
+
if (thumbprint !== expectedThumbprint) throw permanentRotationError("registry KEY_ROTATION new thumbprint does not match the generated pending key");
|
|
217
|
+
}
|
|
218
|
+
async function assertSubmissionMatchesRotation(rotation, submission) {
|
|
219
|
+
if (rotation.submission?.entryHash && submission.entryHash && rotation.submission.entryHash !== submission.entryHash) {
|
|
220
|
+
throw permanentRotationError("registry reconciled a different entry hash for this managed key rotation");
|
|
221
|
+
}
|
|
222
|
+
if (submission.state !== "accepted") return;
|
|
223
|
+
const expectedHash = await sha256Hex(rotation.entryBytes);
|
|
224
|
+
if (submission.entryHash !== expectedHash) {
|
|
225
|
+
throw permanentRotationError("accepted submission entry hash does not match the exact managed key rotation bytes");
|
|
226
|
+
}
|
|
227
|
+
if (submission.keyId !== void 0 && submission.keyId !== rotation.newKid) {
|
|
228
|
+
throw permanentRotationError("accepted submission key_id does not match the generated pending key");
|
|
229
|
+
}
|
|
230
|
+
}
|
|
231
|
+
function structuredRetryWithSameBytes(cause) {
|
|
232
|
+
if (cause && typeof cause === "object" && "retryWithSameBytes" in cause && typeof cause.retryWithSameBytes === "boolean") {
|
|
233
|
+
return cause.retryWithSameBytes;
|
|
234
|
+
}
|
|
235
|
+
return true;
|
|
236
|
+
}
|
|
237
|
+
function permanentRotationError(message, cause) {
|
|
238
|
+
return new VerificationError(message, { code: VerificationCode.LogError, transient: false, cause });
|
|
239
|
+
}
|
|
240
|
+
async function sha256Hex(value) {
|
|
241
|
+
const bytes = value.slice();
|
|
242
|
+
const digest = await crypto.subtle.digest("SHA-256", bytes.buffer);
|
|
243
|
+
return Array.from(new Uint8Array(digest), (byte) => byte.toString(16).padStart(2, "0")).join("");
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
// src/managed-issuance.ts
|
|
247
|
+
import {
|
|
248
|
+
JWKS as JWKS2,
|
|
249
|
+
VerificationCode as VerificationCode2,
|
|
250
|
+
VerificationError as VerificationError2,
|
|
251
|
+
jwkThumbprint as jwkThumbprint2,
|
|
252
|
+
normalizeFQDN
|
|
253
|
+
} from "@dnsid-ai/protocol";
|
|
254
|
+
import {
|
|
255
|
+
c2spTlogEntryBytes as c2spTlogEntryBytes2,
|
|
256
|
+
parsePreparedC2spTlogEvent as parsePreparedC2spTlogEvent2,
|
|
257
|
+
signPreparedC2spTlogEvent as signPreparedC2spTlogEvent2
|
|
258
|
+
} from "@dnsid-ai/log-c2sp-tlog/writer";
|
|
259
|
+
var ManagedIssuanceSubmissionError = class extends VerificationError2 {
|
|
260
|
+
issuance;
|
|
261
|
+
retryWithSameBytes;
|
|
262
|
+
constructor(message, issuance, retryWithSameBytes2, transient, cause) {
|
|
263
|
+
super(message, { code: VerificationCode2.LogError, transient, cause });
|
|
264
|
+
this.name = "ManagedIssuanceSubmissionError";
|
|
265
|
+
this.issuance = issuance;
|
|
266
|
+
this.retryWithSameBytes = retryWithSameBytes2;
|
|
267
|
+
}
|
|
268
|
+
};
|
|
269
|
+
var ManagedIssuanceActivationError = class extends VerificationError2 {
|
|
270
|
+
issuance;
|
|
271
|
+
constructor(issuance, cause) {
|
|
272
|
+
super("managed ISSUANCE was accepted, but publication or activation is incomplete; resume this issuance", {
|
|
273
|
+
code: VerificationCode2.LogError,
|
|
274
|
+
transient: true,
|
|
275
|
+
cause
|
|
276
|
+
});
|
|
277
|
+
this.name = "ManagedIssuanceActivationError";
|
|
278
|
+
this.issuance = issuance;
|
|
279
|
+
}
|
|
280
|
+
};
|
|
281
|
+
async function issueManagedIdentity(options) {
|
|
282
|
+
const domain = normalizeFQDN(options.domain, true);
|
|
283
|
+
const governanceId = normalizeFQDN(options.governanceId, true);
|
|
284
|
+
if (!options.idempotencyKey || options.idempotencyKey.trim() !== options.idempotencyKey || options.idempotencyKey.length > 200) {
|
|
285
|
+
throw permanentLogError("managed ISSUANCE idempotencyKey must contain 1 to 200 characters without surrounding whitespace");
|
|
286
|
+
}
|
|
287
|
+
const existing = await options.loadIssuance();
|
|
288
|
+
const { entityKey, operationalKey } = await trustedKeys(options.entityKey, options.operationalKeyProvider);
|
|
289
|
+
if (existing) {
|
|
290
|
+
assertSameOperation(existing, { ...options, domain, governanceId });
|
|
291
|
+
return resumeManagedIssuanceState(options, existing, entityKey, operationalKey);
|
|
292
|
+
}
|
|
293
|
+
const intent = {
|
|
294
|
+
domain,
|
|
295
|
+
governanceId,
|
|
296
|
+
idempotencyKey: options.idempotencyKey,
|
|
297
|
+
entityKid: entityKey.kid,
|
|
298
|
+
entityThumbprint: await jwkThumbprint2(entityKey),
|
|
299
|
+
operationalKid: operationalKey.kid,
|
|
300
|
+
operationalThumbprint: await jwkThumbprint2(operationalKey),
|
|
301
|
+
activated: false
|
|
302
|
+
};
|
|
303
|
+
const raced = await options.createIssuance(intent);
|
|
304
|
+
if (raced) {
|
|
305
|
+
assertSameOperation(raced, { ...options, domain, governanceId });
|
|
306
|
+
return resumeManagedIssuanceState(options, raced, entityKey, operationalKey);
|
|
307
|
+
}
|
|
308
|
+
return prepareAndSubmit(options, intent, entityKey, operationalKey);
|
|
309
|
+
}
|
|
310
|
+
async function resumeManagedIssuance(options) {
|
|
311
|
+
const issuance = await options.loadIssuance();
|
|
312
|
+
if (!issuance) throw permanentLogError("no durable managed ISSUANCE operation exists");
|
|
313
|
+
const { entityKey, operationalKey } = await trustedKeys(options.entityKey, options.operationalKeyProvider);
|
|
314
|
+
return resumeManagedIssuanceState(options, issuance, entityKey, operationalKey);
|
|
315
|
+
}
|
|
316
|
+
async function resumeManagedIssuanceState(options, issuance, entityKey, operationalKey) {
|
|
317
|
+
await assertPersistedKeys(issuance, entityKey, operationalKey);
|
|
318
|
+
if (issuance.activated) return issuance;
|
|
319
|
+
if (issuance.terminalFailure || issuance.submission?.state === "rejected") {
|
|
320
|
+
throw permanentLogError("cannot resume a terminally failed managed ISSUANCE");
|
|
321
|
+
}
|
|
322
|
+
if (issuance.submission?.state === "accepted") {
|
|
323
|
+
await verifyPersistedCompletedIssuance(issuance, entityKey, operationalKey);
|
|
324
|
+
await assertAcceptedBinding(issuance, issuance.submission);
|
|
325
|
+
return activate(options, issuance);
|
|
326
|
+
}
|
|
327
|
+
if (!issuance.entryBytes || !issuance.logReference) {
|
|
328
|
+
return prepareAndSubmit(options, issuance, entityKey, operationalKey);
|
|
329
|
+
}
|
|
330
|
+
return submit(options, issuance);
|
|
331
|
+
}
|
|
332
|
+
async function prepareAndSubmit(options, intent, entityKey, operationalKey) {
|
|
333
|
+
let raw;
|
|
334
|
+
try {
|
|
335
|
+
raw = await options.registryClient.prepareIssuance(intent.domain, intent.idempotencyKey);
|
|
336
|
+
} catch (cause) {
|
|
337
|
+
if (isTerminalFailure(cause)) {
|
|
338
|
+
const terminal = {
|
|
339
|
+
...intent,
|
|
340
|
+
terminalFailure: { stage: "preparation", errorCode: structuredErrorCode(cause) }
|
|
341
|
+
};
|
|
342
|
+
await options.persistIssuance(terminal);
|
|
343
|
+
throw new ManagedIssuanceSubmissionError("managed ISSUANCE preparation failed terminally", terminal, false, false, cause);
|
|
344
|
+
}
|
|
345
|
+
throw new ManagedIssuanceSubmissionError("managed ISSUANCE preparation is unavailable; resume the durable intent", intent, false, true, cause);
|
|
346
|
+
}
|
|
347
|
+
const context = {
|
|
348
|
+
expectedFqdn: intent.domain,
|
|
349
|
+
expectedGovernanceId: intent.governanceId,
|
|
350
|
+
entityKey,
|
|
351
|
+
operationalKey
|
|
352
|
+
};
|
|
353
|
+
let prepared;
|
|
354
|
+
try {
|
|
355
|
+
if (options.logReference && raw.logReference !== options.logReference) {
|
|
356
|
+
throw permanentLogError(`registry preparation returned unexpected log reference ${raw.logReference}`);
|
|
357
|
+
}
|
|
358
|
+
prepared = await parsePreparedC2spTlogEvent2(raw.entryBytes, raw.logReference, context);
|
|
359
|
+
if (prepared.envelope.type !== "ISSUANCE") {
|
|
360
|
+
throw permanentLogError(`registry preparation returned ${String(prepared.envelope.type)} instead of ISSUANCE`);
|
|
361
|
+
}
|
|
362
|
+
const existingSignatures = prepared.envelope.sigs;
|
|
363
|
+
if (!existingSignatures?.ae) throw permanentLogError("registry ISSUANCE preparation is missing the accountable-entity signature");
|
|
364
|
+
} catch (cause) {
|
|
365
|
+
const terminal = {
|
|
366
|
+
...intent,
|
|
367
|
+
terminalFailure: { stage: "preparation", errorCode: structuredErrorCode(cause) }
|
|
368
|
+
};
|
|
369
|
+
await options.persistIssuance(terminal);
|
|
370
|
+
throw new ManagedIssuanceSubmissionError("managed ISSUANCE preparation failed trusted validation", terminal, false, false, cause);
|
|
371
|
+
}
|
|
372
|
+
prepared = await signPreparedC2spTlogEvent2(prepared, "OperationalCountersignature", options.operationalKeyProvider, context);
|
|
373
|
+
const entryBytes = await c2spTlogEntryBytes2(prepared, context);
|
|
374
|
+
const completed = {
|
|
375
|
+
...intent,
|
|
376
|
+
logReference: raw.logReference,
|
|
377
|
+
entryBytes: entryBytes.slice(),
|
|
378
|
+
entryHash: await sha256Hex2(entryBytes)
|
|
379
|
+
};
|
|
380
|
+
await options.persistIssuance(completed);
|
|
381
|
+
return submit(options, completed);
|
|
382
|
+
}
|
|
383
|
+
async function submit(options, issuance) {
|
|
384
|
+
if (!issuance.entryBytes || !issuance.logReference) throw permanentLogError("managed ISSUANCE exact bytes are not persisted");
|
|
385
|
+
const { entityKey, operationalKey } = await trustedKeys(options.entityKey, options.operationalKeyProvider);
|
|
386
|
+
await verifyPersistedCompletedIssuance(issuance, entityKey, operationalKey);
|
|
387
|
+
let submission;
|
|
388
|
+
try {
|
|
389
|
+
submission = await options.registryClient.submitPreparedEvent(
|
|
390
|
+
issuance.domain,
|
|
391
|
+
issuance.entryBytes.slice(),
|
|
392
|
+
issuance.idempotencyKey
|
|
393
|
+
);
|
|
394
|
+
} catch (cause) {
|
|
395
|
+
const sameBytes = retryWithSameBytes(cause);
|
|
396
|
+
let recoverable = issuance;
|
|
397
|
+
if (sameBytes) {
|
|
398
|
+
recoverable = {
|
|
399
|
+
...issuance,
|
|
400
|
+
submission: {
|
|
401
|
+
state: "indeterminate",
|
|
402
|
+
entryHash: issuance.entryHash,
|
|
403
|
+
errorCode: structuredErrorCode(cause)
|
|
404
|
+
}
|
|
405
|
+
};
|
|
406
|
+
await options.persistIssuance(recoverable);
|
|
407
|
+
} else {
|
|
408
|
+
recoverable = {
|
|
409
|
+
...issuance,
|
|
410
|
+
submission: {
|
|
411
|
+
state: "rejected",
|
|
412
|
+
entryHash: issuance.entryHash,
|
|
413
|
+
errorCode: structuredErrorCode(cause)
|
|
414
|
+
},
|
|
415
|
+
terminalFailure: { stage: "submission", errorCode: structuredErrorCode(cause) }
|
|
416
|
+
};
|
|
417
|
+
await options.persistIssuance(recoverable);
|
|
418
|
+
}
|
|
419
|
+
throw new ManagedIssuanceSubmissionError(
|
|
420
|
+
sameBytes ? "managed ISSUANCE submission is indeterminate; retry the same bytes and idempotency key" : "managed ISSUANCE submission failed terminally",
|
|
421
|
+
recoverable,
|
|
422
|
+
sameBytes,
|
|
423
|
+
sameBytes,
|
|
424
|
+
cause
|
|
425
|
+
);
|
|
426
|
+
}
|
|
427
|
+
await assertAcceptedBinding(issuance, submission);
|
|
428
|
+
const reconciled = { ...issuance, submission };
|
|
429
|
+
await options.persistIssuance(reconciled);
|
|
430
|
+
if (submission.state === "rejected") {
|
|
431
|
+
throw new ManagedIssuanceSubmissionError(
|
|
432
|
+
`registry rejected the prepared ISSUANCE: ${submission.errorCode ?? "unknown error"}`,
|
|
433
|
+
reconciled,
|
|
434
|
+
false,
|
|
435
|
+
false
|
|
436
|
+
);
|
|
437
|
+
}
|
|
438
|
+
if (submission.state !== "accepted") return reconciled;
|
|
439
|
+
return activate(options, reconciled);
|
|
440
|
+
}
|
|
441
|
+
async function activate(options, issuance) {
|
|
442
|
+
try {
|
|
443
|
+
await options.activateAcceptedIssuance(issuance);
|
|
444
|
+
const activated = { ...issuance, activated: true };
|
|
445
|
+
await options.persistIssuance(activated);
|
|
446
|
+
return activated;
|
|
447
|
+
} catch (cause) {
|
|
448
|
+
throw new ManagedIssuanceActivationError(issuance, cause);
|
|
449
|
+
}
|
|
450
|
+
}
|
|
451
|
+
async function trustedKeys(entityKey, operationalProvider) {
|
|
452
|
+
const operationalKey = await operationalProvider.signingKey();
|
|
453
|
+
new JWKS2([entityKey]).validateRecordSigning();
|
|
454
|
+
new JWKS2([operationalKey]).validateOperational();
|
|
455
|
+
if (await jwkThumbprint2(entityKey) === await jwkThumbprint2(operationalKey)) {
|
|
456
|
+
throw permanentLogError("managed ISSUANCE requires distinct entity and operational keys");
|
|
457
|
+
}
|
|
458
|
+
return { entityKey, operationalKey };
|
|
459
|
+
}
|
|
460
|
+
async function assertPersistedKeys(state, entityKey, operationalKey) {
|
|
461
|
+
if (state.entityKid !== entityKey.kid || state.entityThumbprint !== await jwkThumbprint2(entityKey)) {
|
|
462
|
+
throw permanentLogError("durable managed ISSUANCE entity key does not match the trusted provider");
|
|
463
|
+
}
|
|
464
|
+
if (state.operationalKid !== operationalKey.kid || state.operationalThumbprint !== await jwkThumbprint2(operationalKey)) {
|
|
465
|
+
throw permanentLogError("durable managed ISSUANCE operational key does not match the trusted provider");
|
|
466
|
+
}
|
|
467
|
+
}
|
|
468
|
+
async function verifyPersistedCompletedIssuance(state, entityKey, operationalKey) {
|
|
469
|
+
if (!state.entryBytes || !state.entryHash || !state.logReference) {
|
|
470
|
+
throw permanentLogError("durable managed ISSUANCE is missing exact bytes, hash, or log reference");
|
|
471
|
+
}
|
|
472
|
+
const actualHash = await sha256Hex2(state.entryBytes);
|
|
473
|
+
if (actualHash !== state.entryHash) throw permanentLogError("durable managed ISSUANCE bytes do not match their persisted hash");
|
|
474
|
+
const context = {
|
|
475
|
+
expectedFqdn: state.domain,
|
|
476
|
+
expectedGovernanceId: state.governanceId,
|
|
477
|
+
entityKey,
|
|
478
|
+
operationalKey
|
|
479
|
+
};
|
|
480
|
+
const prepared = await parsePreparedC2spTlogEvent2(state.entryBytes, state.logReference, context);
|
|
481
|
+
const canonical = await c2spTlogEntryBytes2(prepared, context);
|
|
482
|
+
if (!bytesEqual(canonical, state.entryBytes)) throw permanentLogError("durable managed ISSUANCE bytes are not the canonical completed entry");
|
|
483
|
+
}
|
|
484
|
+
function assertSameOperation(state, options) {
|
|
485
|
+
if (state.domain !== options.domain || state.governanceId !== options.governanceId || state.idempotencyKey !== options.idempotencyKey) {
|
|
486
|
+
throw permanentLogError("a different durable managed ISSUANCE operation already exists");
|
|
487
|
+
}
|
|
488
|
+
}
|
|
489
|
+
async function assertAcceptedBinding(issuance, submission) {
|
|
490
|
+
if (issuance.submission?.entryHash && submission.entryHash !== issuance.submission.entryHash) {
|
|
491
|
+
throw permanentLogError("registry reconciled a different entry hash for this managed ISSUANCE");
|
|
492
|
+
}
|
|
493
|
+
if (submission.state !== "accepted") return;
|
|
494
|
+
const expectedHash = await sha256Hex2(issuance.entryBytes);
|
|
495
|
+
if (submission.entryHash !== expectedHash) throw permanentLogError("accepted entry hash does not match the exact managed ISSUANCE bytes");
|
|
496
|
+
if (!Number.isSafeInteger(submission.index) || submission.index < 0) throw permanentLogError("accepted managed ISSUANCE is missing a valid index");
|
|
497
|
+
const expectedReference = `${issuance.logReference}@${submission.index}`;
|
|
498
|
+
if (submission.logRef !== expectedReference) throw permanentLogError("accepted managed ISSUANCE final log reference does not match its prepared binding");
|
|
499
|
+
}
|
|
500
|
+
function retryWithSameBytes(cause) {
|
|
501
|
+
return !(cause && typeof cause === "object" && "retryWithSameBytes" in cause && cause.retryWithSameBytes === false);
|
|
502
|
+
}
|
|
503
|
+
function isTerminalFailure(cause) {
|
|
504
|
+
if (!cause || typeof cause !== "object") return false;
|
|
505
|
+
if ("retryable" in cause && cause.retryable === false) return true;
|
|
506
|
+
if ("state" in cause && cause.state === "rejected") return true;
|
|
507
|
+
return "retryWithSameBytes" in cause && cause.retryWithSameBytes === false;
|
|
508
|
+
}
|
|
509
|
+
function structuredErrorCode(cause) {
|
|
510
|
+
if (cause && typeof cause === "object" && "code" in cause && typeof cause.code === "string") {
|
|
511
|
+
return cause.code;
|
|
512
|
+
}
|
|
513
|
+
return void 0;
|
|
514
|
+
}
|
|
515
|
+
function bytesEqual(left, right) {
|
|
516
|
+
return left.length === right.length && left.every((byte, index) => byte === right[index]);
|
|
517
|
+
}
|
|
518
|
+
function permanentLogError(message, cause) {
|
|
519
|
+
return new VerificationError2(message, { code: VerificationCode2.LogError, transient: false, cause });
|
|
520
|
+
}
|
|
521
|
+
async function sha256Hex2(bytes) {
|
|
522
|
+
const copy = bytes.slice();
|
|
523
|
+
const digest = await crypto.subtle.digest("SHA-256", copy.buffer);
|
|
524
|
+
return Array.from(new Uint8Array(digest), (byte) => byte.toString(16).padStart(2, "0")).join("");
|
|
525
|
+
}
|
|
526
|
+
|
|
527
|
+
// src/index.ts
|
|
528
|
+
import { WebBotAuthProfile, createWebBotAuthProfile } from "@dnsid-ai/web-bot-auth";
|
|
529
|
+
|
|
530
|
+
// src/conformance.ts
|
|
531
|
+
import {
|
|
532
|
+
DEFAULT_PUBLISH_PROFILE,
|
|
533
|
+
DNSID_DRAFT01_VERSION,
|
|
534
|
+
DNSID_VERSION
|
|
535
|
+
} from "@dnsid-ai/protocol";
|
|
536
|
+
import { C2SP_TLOG_PROFILE_VERSION, C2SP_TLOG_SPECIFICATIONS, DNSID_C2SP_METHOD_REVISION } from "@dnsid-ai/log-c2sp-tlog/version";
|
|
537
|
+
var c2spTlogConformance = [
|
|
538
|
+
`profile=${C2SP_TLOG_PROFILE_VERSION}`,
|
|
539
|
+
...Object.entries(C2SP_TLOG_SPECIFICATIONS).map(([name, revision]) => `${name}=${revision}`),
|
|
540
|
+
`dnsid-method=${DNSID_C2SP_METHOD_REVISION}`
|
|
541
|
+
].join(";");
|
|
542
|
+
var SDK_CONFORMANCE = Object.freeze({
|
|
543
|
+
publishProfile: DEFAULT_PUBLISH_PROFILE,
|
|
544
|
+
verificationProfiles: Object.freeze({
|
|
545
|
+
[DNSID_DRAFT01_VERSION]: DNSID_DRAFT01_VERSION,
|
|
546
|
+
[DNSID_VERSION]: DNSID_DRAFT01_VERSION
|
|
547
|
+
}),
|
|
548
|
+
specificationStatus: "internet-draft",
|
|
549
|
+
logBindings: Object.freeze({
|
|
550
|
+
"c2sp-tlog": c2spTlogConformance
|
|
551
|
+
}),
|
|
552
|
+
knownDeviations: Object.freeze([])
|
|
553
|
+
});
|
|
554
|
+
|
|
555
|
+
// src/index.ts
|
|
556
|
+
function createIdentityManager(config, deps) {
|
|
557
|
+
return new IdentityManager(config, deps);
|
|
558
|
+
}
|
|
559
|
+
function createIdentityVerifier(config, deps) {
|
|
560
|
+
return new IdentityManager(config, deps);
|
|
561
|
+
}
|
|
562
|
+
export {
|
|
563
|
+
ArgumentError,
|
|
564
|
+
DEFAULT_REGISTRY_URL,
|
|
565
|
+
DNSID_VERSION2 as DNSID_VERSION,
|
|
566
|
+
DNSSECMode,
|
|
567
|
+
DNSSECState,
|
|
568
|
+
DnsIdTxtRecord,
|
|
569
|
+
DomainLog,
|
|
570
|
+
DomainSnapshot,
|
|
571
|
+
HttpSignaturesProfile,
|
|
572
|
+
IdentityManager2 as IdentityManager,
|
|
573
|
+
InMemoryIdentityCache,
|
|
574
|
+
JWKS3 as JWKS,
|
|
575
|
+
JWKS_MAX_RESPONSE_BYTES,
|
|
576
|
+
JoseProfile,
|
|
577
|
+
LogRegistry2 as LogRegistry,
|
|
578
|
+
ManagedIssuanceActivationError,
|
|
579
|
+
ManagedIssuanceSubmissionError,
|
|
580
|
+
ManagedKeyRotationActivationError,
|
|
581
|
+
ManagedKeyRotationSubmissionError,
|
|
582
|
+
NoopLogReader,
|
|
583
|
+
ParseError,
|
|
584
|
+
PreparedEventSubmissionError,
|
|
585
|
+
RegistryClient,
|
|
586
|
+
RegistryWorkflowError,
|
|
587
|
+
SDK_CONFORMANCE,
|
|
588
|
+
SIGNING_ALGS,
|
|
589
|
+
STATUS_MAX_RESPONSE_BYTES,
|
|
590
|
+
ValidationError,
|
|
591
|
+
VerificationCode3 as VerificationCode,
|
|
592
|
+
VerificationError3 as VerificationError,
|
|
593
|
+
VerifiedDomain,
|
|
594
|
+
WebBotAuthProfile,
|
|
595
|
+
activeStatusDocument,
|
|
596
|
+
awaitRegistryManagedPublication,
|
|
597
|
+
createHttpSignaturesProfile,
|
|
598
|
+
createIdentityManager,
|
|
599
|
+
createIdentityVerifier,
|
|
600
|
+
createJoseProfile,
|
|
601
|
+
createWebBotAuthProfile,
|
|
602
|
+
fromBase64Url,
|
|
603
|
+
httpSignatures,
|
|
604
|
+
isDomainName,
|
|
605
|
+
isTransientVerificationError,
|
|
606
|
+
issueManagedIdentity,
|
|
607
|
+
jose,
|
|
608
|
+
jwkSignatureAlg,
|
|
609
|
+
jwkThumbprint3 as jwkThumbprint,
|
|
610
|
+
normalizeFQDN2 as normalizeFQDN,
|
|
611
|
+
parseKaDuration,
|
|
612
|
+
parseKeyId,
|
|
613
|
+
publishClientControlledRecord,
|
|
614
|
+
publishToRegistry,
|
|
615
|
+
registry,
|
|
616
|
+
resumeManagedIssuance,
|
|
617
|
+
resumeManagedOperationalKeyRotation,
|
|
618
|
+
retryTransientVerification,
|
|
619
|
+
rotateManagedOperationalKey,
|
|
620
|
+
toArrayBuffer,
|
|
621
|
+
toBase64Url,
|
|
622
|
+
validateAgentStatus,
|
|
623
|
+
verifyWithKey,
|
|
624
|
+
webBotAuth
|
|
625
|
+
};
|