@layervai/qurl 0.6.0 → 2.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +332 -15
- package/dist/cjs/client.d.ts +62 -31
- package/dist/cjs/client.js +932 -163
- package/dist/cjs/crid.d.ts +3 -0
- package/dist/cjs/crid.js +62 -0
- package/dist/cjs/errors.d.ts +30 -5
- package/dist/cjs/errors.js +41 -6
- package/dist/cjs/index.d.ts +5 -3
- package/dist/cjs/index.js +11 -1
- package/dist/cjs/node/agent-runtime.d.ts +108 -0
- package/dist/cjs/node/agent-runtime.js +952 -0
- package/dist/cjs/node/agent-state.d.ts +100 -0
- package/dist/cjs/node/agent-state.js +311 -0
- package/dist/cjs/node/agent-transport.d.ts +17 -0
- package/dist/cjs/node/agent-transport.js +118 -0
- package/dist/cjs/node/deployment.d.ts +6 -0
- package/dist/cjs/node/deployment.js +24 -3
- package/dist/cjs/node/file-agent-state.d.ts +15 -0
- package/dist/cjs/node/file-agent-state.js +137 -0
- package/dist/cjs/node/native-loader.cjs +7 -0
- package/dist/cjs/node/native-loader.d.cts +11 -0
- package/dist/cjs/node/native-udp.d.ts +8 -3
- package/dist/cjs/node/native-udp.js +8 -3
- package/dist/cjs/node/nhp-wire.d.ts +9 -0
- package/dist/cjs/node/nhp-wire.js +24 -7
- package/dist/cjs/node/portal-opener.d.ts +7 -0
- package/dist/cjs/node/portal-opener.js +28 -6
- package/dist/cjs/node/provider.d.ts +19 -0
- package/dist/cjs/node/provider.js +153 -0
- package/dist/cjs/node/qv2.d.ts +3 -1
- package/dist/cjs/node/qv2.js +16 -2
- package/dist/cjs/node/relay.d.ts +12 -0
- package/dist/cjs/node/relay.js +111 -0
- package/dist/cjs/node/sealed-agent-state.d.ts +19 -0
- package/dist/cjs/node/sealed-agent-state.js +193 -0
- package/dist/cjs/node/strict-json.d.ts +1 -1
- package/dist/cjs/node/strict-json.js +8 -3
- package/dist/cjs/node.d.ts +14 -0
- package/dist/cjs/node.js +24 -1
- package/dist/cjs/share.d.ts +56 -0
- package/dist/cjs/share.js +113 -0
- package/dist/cjs/types.d.ts +173 -13
- package/dist/cjs/version.d.ts +1 -1
- package/dist/cjs/version.js +1 -1
- package/dist/esm/client.d.ts +62 -31
- package/dist/esm/client.d.ts.map +1 -1
- package/dist/esm/client.js +932 -164
- package/dist/esm/client.js.map +1 -1
- package/dist/esm/crid.d.ts +4 -0
- package/dist/esm/crid.d.ts.map +1 -0
- package/dist/esm/crid.js +58 -0
- package/dist/esm/crid.js.map +1 -0
- package/dist/esm/errors.d.ts +30 -5
- package/dist/esm/errors.d.ts.map +1 -1
- package/dist/esm/errors.js +39 -5
- package/dist/esm/errors.js.map +1 -1
- package/dist/esm/index.d.ts +5 -3
- package/dist/esm/index.d.ts.map +1 -1
- package/dist/esm/index.js +3 -2
- package/dist/esm/index.js.map +1 -1
- package/dist/esm/node/agent-runtime.d.ts +109 -0
- package/dist/esm/node/agent-runtime.d.ts.map +1 -0
- package/dist/esm/node/agent-runtime.js +946 -0
- package/dist/esm/node/agent-runtime.js.map +1 -0
- package/dist/esm/node/agent-state.d.ts +101 -0
- package/dist/esm/node/agent-state.d.ts.map +1 -0
- package/dist/esm/node/agent-state.js +298 -0
- package/dist/esm/node/agent-state.js.map +1 -0
- package/dist/esm/node/agent-transport.d.ts +18 -0
- package/dist/esm/node/agent-transport.d.ts.map +1 -0
- package/dist/esm/node/agent-transport.js +114 -0
- package/dist/esm/node/agent-transport.js.map +1 -0
- package/dist/esm/node/deployment.d.ts +6 -0
- package/dist/esm/node/deployment.d.ts.map +1 -1
- package/dist/esm/node/deployment.js +23 -3
- package/dist/esm/node/deployment.js.map +1 -1
- package/dist/esm/node/file-agent-state.d.ts +16 -0
- package/dist/esm/node/file-agent-state.d.ts.map +1 -0
- package/dist/esm/node/file-agent-state.js +134 -0
- package/dist/esm/node/file-agent-state.js.map +1 -0
- package/dist/esm/node/native-loader.cjs +7 -0
- package/dist/esm/node/native-loader.d.cts +11 -0
- package/dist/esm/node/native-udp.d.ts +8 -3
- package/dist/esm/node/native-udp.d.ts.map +1 -1
- package/dist/esm/node/native-udp.js +7 -5
- package/dist/esm/node/native-udp.js.map +1 -1
- package/dist/esm/node/nhp-wire.d.ts +9 -0
- package/dist/esm/node/nhp-wire.d.ts.map +1 -1
- package/dist/esm/node/nhp-wire.js +23 -6
- package/dist/esm/node/nhp-wire.js.map +1 -1
- package/dist/esm/node/portal-opener.d.ts +7 -0
- package/dist/esm/node/portal-opener.d.ts.map +1 -1
- package/dist/esm/node/portal-opener.js +28 -6
- package/dist/esm/node/portal-opener.js.map +1 -1
- package/dist/esm/node/provider.d.ts +20 -0
- package/dist/esm/node/provider.d.ts.map +1 -0
- package/dist/esm/node/provider.js +149 -0
- package/dist/esm/node/provider.js.map +1 -0
- package/dist/esm/node/qv2.d.ts +3 -1
- package/dist/esm/node/qv2.d.ts.map +1 -1
- package/dist/esm/node/qv2.js +17 -3
- package/dist/esm/node/qv2.js.map +1 -1
- package/dist/esm/node/relay.d.ts +13 -0
- package/dist/esm/node/relay.d.ts.map +1 -0
- package/dist/esm/node/relay.js +105 -0
- package/dist/esm/node/relay.js.map +1 -0
- package/dist/esm/node/sealed-agent-state.d.ts +20 -0
- package/dist/esm/node/sealed-agent-state.d.ts.map +1 -0
- package/dist/esm/node/sealed-agent-state.js +190 -0
- package/dist/esm/node/sealed-agent-state.js.map +1 -0
- package/dist/esm/node/strict-json.d.ts +1 -1
- package/dist/esm/node/strict-json.d.ts.map +1 -1
- package/dist/esm/node/strict-json.js +8 -3
- package/dist/esm/node/strict-json.js.map +1 -1
- package/dist/esm/node.d.ts +14 -0
- package/dist/esm/node.d.ts.map +1 -1
- package/dist/esm/node.js +7 -0
- package/dist/esm/node.js.map +1 -1
- package/dist/esm/share.d.ts +57 -0
- package/dist/esm/share.d.ts.map +1 -0
- package/dist/esm/share.js +109 -0
- package/dist/esm/share.js.map +1 -0
- package/dist/esm/types.d.ts +173 -13
- package/dist/esm/types.d.ts.map +1 -1
- package/dist/esm/version.d.ts +1 -1
- package/dist/esm/version.js +1 -1
- package/package.json +17 -8
package/dist/cjs/node/qv2.js
CHANGED
|
@@ -7,6 +7,7 @@ exports.x25519PublicFromPrivate = x25519PublicFromPrivate;
|
|
|
7
7
|
exports.strictBase64Url = strictBase64Url;
|
|
8
8
|
const node_crypto_1 = require("node:crypto");
|
|
9
9
|
const strict_json_js_1 = require("./strict-json.js");
|
|
10
|
+
const crid_js_1 = require("../crid.js");
|
|
10
11
|
const TRANSPORT_PREFIX = "qv2t1";
|
|
11
12
|
const FRAGMENT_PREFIX = "qv2";
|
|
12
13
|
const TRANSPORT_MAX_LENGTH = 6_826;
|
|
@@ -30,7 +31,7 @@ const CLAIM_KEYS = new Set([
|
|
|
30
31
|
"qurl_user_public_key_b64",
|
|
31
32
|
]);
|
|
32
33
|
const REQUIRED_CLAIM_KEYS = [...CLAIM_KEYS].filter((key) => key !== "cell_id");
|
|
33
|
-
function verifyQv2Link(qurl, issuers) {
|
|
34
|
+
function verifyQv2Link(qurl, issuers, expectedCRID) {
|
|
34
35
|
const hash = qurl.indexOf("#");
|
|
35
36
|
if (hash < 0)
|
|
36
37
|
throw new Error("qURL link has no credential fragment");
|
|
@@ -45,6 +46,18 @@ function verifyQv2Link(qurl, issuers) {
|
|
|
45
46
|
const claims = parseClaims(decodeCanonicalBase64Url(claimsB64));
|
|
46
47
|
const secret = parseAndWipeSecret(decodeCanonicalBase64Url(secretB64));
|
|
47
48
|
verifyIssuerClaims(claimsB64, signatureB64, issuers, claims);
|
|
49
|
+
if (expectedCRID !== undefined) {
|
|
50
|
+
const expected = (0, crid_js_1.parseCrid)(expectedCRID, true);
|
|
51
|
+
if (!expected)
|
|
52
|
+
throw new Error("invalid or unsupported expected CRID");
|
|
53
|
+
// The native verifier is synchronous; the portable helper uses WebCrypto.
|
|
54
|
+
const digest = (0, node_crypto_1.createHash)("sha256")
|
|
55
|
+
.update(crid_js_1.CRID_DOMAIN)
|
|
56
|
+
.update(decodeCanonicalBase64Url(claims.resourcePublicKeyB64))
|
|
57
|
+
.digest();
|
|
58
|
+
if (!(0, node_crypto_1.timingSafeEqual)(expected, digest.subarray(0, expected.length)))
|
|
59
|
+
throw new Error("qURL resource key does not match the expected CRID");
|
|
60
|
+
}
|
|
48
61
|
const privateKey = decodeCanonicalBase64Url(secret.qurlUserPrivateKeyB64);
|
|
49
62
|
let retainPrivateKey = false;
|
|
50
63
|
try {
|
|
@@ -63,6 +76,7 @@ function verifyQv2Link(qurl, issuers) {
|
|
|
63
76
|
kid: claims.kid,
|
|
64
77
|
cellPublicKey: decodeX25519(claims.cellPublicKeyB64, "cell public key"),
|
|
65
78
|
resourcePublicKeyB64: claims.resourcePublicKeyB64,
|
|
79
|
+
relayUrl: claims.relayUrl,
|
|
66
80
|
},
|
|
67
81
|
devicePrivateKey: privateKey,
|
|
68
82
|
};
|
|
@@ -207,7 +221,6 @@ function parseClaims(raw) {
|
|
|
207
221
|
if (resourcePublic.byteLength < 80 || resourcePublic.byteLength > 160) {
|
|
208
222
|
throw new Error("resource public key has an invalid length");
|
|
209
223
|
}
|
|
210
|
-
requireNonEmptyString(value.relay_url, "relay URL");
|
|
211
224
|
// cell_id is descriptive deployment metadata, not a security identity. The
|
|
212
225
|
// signed 32-byte cell public key selects and then fully binds the UDP peer.
|
|
213
226
|
if ("cell_id" in value && typeof value.cell_id !== "string") {
|
|
@@ -218,6 +231,7 @@ function parseClaims(raw) {
|
|
|
218
231
|
cellPublicKeyB64,
|
|
219
232
|
resourcePublicKeyB64,
|
|
220
233
|
qurlUserPublicKeyB64: qurlPublic,
|
|
234
|
+
relayUrl: requireNonEmptyString(value.relay_url, "relay URL"),
|
|
221
235
|
};
|
|
222
236
|
}
|
|
223
237
|
function verifyIssuerClaims(claimsB64, signatureB64, issuers, parsedClaims) {
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import { type NHPMessage } from "./nhp-wire.js";
|
|
2
|
+
export declare class RelayError extends Error {
|
|
3
|
+
readonly status: number;
|
|
4
|
+
constructor(status: number, message: string, options?: ErrorOptions);
|
|
5
|
+
}
|
|
6
|
+
/** Call only after verifying the issuer signature over the relay URL. */
|
|
7
|
+
export declare function validateRelayURL(value: string, allowlist: readonly string[]): URL;
|
|
8
|
+
export declare function readBoundedBody(response: Response, limit: number): Promise<Uint8Array>;
|
|
9
|
+
export declare function relayKnock(relayURL: string, allowlist: readonly string[], serverPublicKey: Uint8Array, devicePrivateKey: Uint8Array, body: Uint8Array, options?: {
|
|
10
|
+
signal?: AbortSignal;
|
|
11
|
+
fetch?: typeof globalThis.fetch;
|
|
12
|
+
}): Promise<NHPMessage>;
|
|
@@ -0,0 +1,111 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.RelayError = void 0;
|
|
4
|
+
exports.validateRelayURL = validateRelayURL;
|
|
5
|
+
exports.readBoundedBody = readBoundedBody;
|
|
6
|
+
exports.relayKnock = relayKnock;
|
|
7
|
+
const deployment_js_1 = require("./deployment.js");
|
|
8
|
+
const nhp_wire_js_1 = require("./nhp-wire.js");
|
|
9
|
+
class RelayError extends Error {
|
|
10
|
+
status;
|
|
11
|
+
constructor(status, message, options) {
|
|
12
|
+
super(message, options);
|
|
13
|
+
this.status = status;
|
|
14
|
+
this.name = "RelayError";
|
|
15
|
+
}
|
|
16
|
+
}
|
|
17
|
+
exports.RelayError = RelayError;
|
|
18
|
+
/** Call only after verifying the issuer signature over the relay URL. */
|
|
19
|
+
function validateRelayURL(value, allowlist) {
|
|
20
|
+
const url = new URL(value);
|
|
21
|
+
if (url.protocol !== "https:" ||
|
|
22
|
+
url.username ||
|
|
23
|
+
url.password ||
|
|
24
|
+
!url.hostname ||
|
|
25
|
+
!allowlist.some((entry) => {
|
|
26
|
+
const host = entry.trim().toLowerCase();
|
|
27
|
+
return (host === url.host.toLowerCase() ||
|
|
28
|
+
host === url.hostname.toLowerCase() ||
|
|
29
|
+
(url.port === "" && host === `${url.hostname.toLowerCase()}:443`));
|
|
30
|
+
}))
|
|
31
|
+
throw new Error("qURL relay URL is not permitted by deployment trust");
|
|
32
|
+
if (url.search || url.hash)
|
|
33
|
+
throw new Error("qURL relay base URL must not contain a query or fragment");
|
|
34
|
+
return url;
|
|
35
|
+
}
|
|
36
|
+
async function readBoundedBody(response, limit) {
|
|
37
|
+
if (!response.body)
|
|
38
|
+
return new Uint8Array();
|
|
39
|
+
const reader = response.body.getReader();
|
|
40
|
+
const chunks = [];
|
|
41
|
+
let length = 0;
|
|
42
|
+
try {
|
|
43
|
+
for (;;) {
|
|
44
|
+
const { done, value } = await reader.read();
|
|
45
|
+
if (done)
|
|
46
|
+
return Buffer.concat(chunks, length);
|
|
47
|
+
length += value.byteLength;
|
|
48
|
+
if (length > limit)
|
|
49
|
+
throw new Error("qURL HTTP response exceeds its size limit");
|
|
50
|
+
chunks.push(value);
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
finally {
|
|
54
|
+
await reader.cancel().catch(() => undefined);
|
|
55
|
+
reader.releaseLock();
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
async function relayKnock(relayURL, allowlist, serverPublicKey, devicePrivateKey, body, options = {}) {
|
|
59
|
+
let base;
|
|
60
|
+
try {
|
|
61
|
+
base = validateRelayURL(relayURL, allowlist);
|
|
62
|
+
}
|
|
63
|
+
catch (cause) {
|
|
64
|
+
throw new RelayError(0, "qURL relay URL is invalid or not trusted", { cause });
|
|
65
|
+
}
|
|
66
|
+
options.signal?.throwIfAborted();
|
|
67
|
+
const built = (0, nhp_wire_js_1.buildNHPMessage)({ type: nhp_wire_js_1.NHP_TYPE_KNOCK, serverPublicKey, devicePrivateKey, body });
|
|
68
|
+
let packet;
|
|
69
|
+
try {
|
|
70
|
+
const url = `${base.href.replace(/\/+$/, "")}/relay/${(0, deployment_js_1.fingerprintKey)(serverPublicKey)}`;
|
|
71
|
+
let response;
|
|
72
|
+
try {
|
|
73
|
+
response = await (options.fetch ?? globalThis.fetch)(url, {
|
|
74
|
+
method: "POST",
|
|
75
|
+
headers: { "Content-Type": "application/octet-stream" },
|
|
76
|
+
body: Buffer.from(built.packet),
|
|
77
|
+
redirect: "error",
|
|
78
|
+
signal: options.signal
|
|
79
|
+
? AbortSignal.any([options.signal, AbortSignal.timeout(15_000)])
|
|
80
|
+
: AbortSignal.timeout(15_000),
|
|
81
|
+
});
|
|
82
|
+
}
|
|
83
|
+
catch (cause) {
|
|
84
|
+
options.signal?.throwIfAborted();
|
|
85
|
+
throw new RelayError(0, "qURL relay transport failed", { cause });
|
|
86
|
+
}
|
|
87
|
+
if (response.status !== 200) {
|
|
88
|
+
await response.body?.cancel().catch(() => undefined);
|
|
89
|
+
throw new RelayError(response.status, "qURL relay returned an unexpected HTTP status");
|
|
90
|
+
}
|
|
91
|
+
try {
|
|
92
|
+
packet = await readBoundedBody(response, nhp_wire_js_1.NHP_PACKET_SIZE);
|
|
93
|
+
const reply = (0, nhp_wire_js_1.decryptNHPReply)(devicePrivateKey, serverPublicKey, packet);
|
|
94
|
+
if (reply.type === nhp_wire_js_1.NHP_TYPE_COOKIE)
|
|
95
|
+
return reply;
|
|
96
|
+
if (reply.type !== nhp_wire_js_1.NHP_TYPE_ACK || reply.counter !== built.counter) {
|
|
97
|
+
reply.body.fill(0);
|
|
98
|
+
throw new Error("NHP relay reply does not match the request");
|
|
99
|
+
}
|
|
100
|
+
return reply;
|
|
101
|
+
}
|
|
102
|
+
catch (cause) {
|
|
103
|
+
options.signal?.throwIfAborted();
|
|
104
|
+
throw new RelayError(200, "qURL relay reply is invalid", { cause });
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
finally {
|
|
108
|
+
built.packet.fill(0);
|
|
109
|
+
packet?.fill(0);
|
|
110
|
+
}
|
|
111
|
+
}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import { FileAgentState, type AgentStateCodec } from "./file-agent-state.js";
|
|
2
|
+
import { type StrictJsonValue } from "./strict-json.js";
|
|
3
|
+
export interface AgentStateKeyBinding {
|
|
4
|
+
purpose: string;
|
|
5
|
+
envelopeVersion: number;
|
|
6
|
+
providerID: string;
|
|
7
|
+
agentID: string;
|
|
8
|
+
}
|
|
9
|
+
export interface WrappedAgentStateKey {
|
|
10
|
+
version: number;
|
|
11
|
+
ciphertext: Uint8Array;
|
|
12
|
+
metadata?: StrictJsonValue;
|
|
13
|
+
}
|
|
14
|
+
export interface AgentStateKeyWrapper {
|
|
15
|
+
wrapKey(key: Uint8Array, binding: AgentStateKeyBinding, signal?: AbortSignal): Promise<WrappedAgentStateKey>;
|
|
16
|
+
unwrapKey(wrapped: WrappedAgentStateKey, binding: AgentStateKeyBinding, signal?: AbortSignal): Promise<Uint8Array>;
|
|
17
|
+
}
|
|
18
|
+
export declare function createSealedAgentStateCodec(providerID: string, wrapper: AgentStateKeyWrapper, expectedAgentID?: string): AgentStateCodec;
|
|
19
|
+
export declare function openSealedFileAgentState(path: string, providerID: string, wrapper: AgentStateKeyWrapper, expectedAgentID?: string): FileAgentState;
|
|
@@ -0,0 +1,193 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.createSealedAgentStateCodec = createSealedAgentStateCodec;
|
|
4
|
+
exports.openSealedFileAgentState = openSealedFileAgentState;
|
|
5
|
+
const node_crypto_1 = require("node:crypto");
|
|
6
|
+
const agent_state_js_1 = require("./agent-state.js");
|
|
7
|
+
const file_agent_state_js_1 = require("./file-agent-state.js");
|
|
8
|
+
const strict_json_js_1 = require("./strict-json.js");
|
|
9
|
+
const PURPOSE = "qurl-go/agent-state";
|
|
10
|
+
function base64(value, maximum) {
|
|
11
|
+
if (typeof value !== "string")
|
|
12
|
+
throw new agent_state_js_1.AgentStateError("INVALID_ENVELOPE");
|
|
13
|
+
const bytes = Buffer.from(value, "base64");
|
|
14
|
+
if (bytes.toString("base64") !== value || bytes.length === 0 || bytes.length > maximum)
|
|
15
|
+
throw new agent_state_js_1.AgentStateError("INVALID_ENVELOPE");
|
|
16
|
+
return bytes;
|
|
17
|
+
}
|
|
18
|
+
// Go authenticates compact RawMessage bytes: keep key order and number lexemes.
|
|
19
|
+
function compactMetadata(raw) {
|
|
20
|
+
return raw.replace(/"(?:\\.|[^"\\])*"|\s+/g, (token) => token.startsWith('"')
|
|
21
|
+
? token.replace(/[<>&\u2028\u2029]/g, (ch) => `\\u${ch.charCodeAt(0).toString(16).padStart(4, "0")}`)
|
|
22
|
+
: "");
|
|
23
|
+
}
|
|
24
|
+
function wrappedWire(wrapped) {
|
|
25
|
+
if (!Number.isSafeInteger(wrapped.version) ||
|
|
26
|
+
wrapped.version < 1 ||
|
|
27
|
+
!(wrapped.ciphertext instanceof Uint8Array) ||
|
|
28
|
+
wrapped.ciphertext.length === 0 ||
|
|
29
|
+
wrapped.ciphertext.length > 65536)
|
|
30
|
+
throw new agent_state_js_1.AgentStateError("INVALID_WRAPPED_KEY");
|
|
31
|
+
const metadata = wrapped.metadata === undefined
|
|
32
|
+
? undefined
|
|
33
|
+
: (0, strict_json_js_1.parseStrictJson)((0, agent_state_js_1.encodeAgentJSON)(wrapped.metadata), 16384);
|
|
34
|
+
return {
|
|
35
|
+
version: wrapped.version,
|
|
36
|
+
ciphertext: Buffer.from(wrapped.ciphertext).toString("base64"),
|
|
37
|
+
...(metadata === undefined ? {} : { metadata }),
|
|
38
|
+
};
|
|
39
|
+
}
|
|
40
|
+
function aad(binding, wrapped, metadataRaw) {
|
|
41
|
+
const raw = (0, agent_state_js_1.encodeAgentJSON)({
|
|
42
|
+
purpose: binding.purpose,
|
|
43
|
+
envelope_version: binding.envelopeVersion,
|
|
44
|
+
provider_id: binding.providerID,
|
|
45
|
+
agent_id: binding.agentID,
|
|
46
|
+
wrapped_key: { version: wrapped.version, ciphertext: wrapped.ciphertext },
|
|
47
|
+
});
|
|
48
|
+
const metadata = metadataRaw === undefined
|
|
49
|
+
? wrapped.metadata === undefined
|
|
50
|
+
? undefined
|
|
51
|
+
: (0, agent_state_js_1.encodeAgentJSON)(wrapped.metadata).toString()
|
|
52
|
+
: compactMetadata(metadataRaw);
|
|
53
|
+
// wrapped_key is the final field; raw ends with its closing brace and the envelope brace.
|
|
54
|
+
return metadata === undefined
|
|
55
|
+
? raw
|
|
56
|
+
: Buffer.concat([raw.subarray(0, -2), Buffer.from(',"metadata":' + metadata + "}}")]);
|
|
57
|
+
}
|
|
58
|
+
function decrypt(key, nonce, ciphertext, associated) {
|
|
59
|
+
if (key.length !== 32 || nonce.length !== 12 || ciphertext.length < 16)
|
|
60
|
+
throw new agent_state_js_1.AgentStateError("INVALID_ENVELOPE");
|
|
61
|
+
const cipher = (0, node_crypto_1.createDecipheriv)("aes-256-gcm", key, nonce);
|
|
62
|
+
cipher.setAAD(associated);
|
|
63
|
+
cipher.setAuthTag(ciphertext.subarray(-16));
|
|
64
|
+
let plain;
|
|
65
|
+
try {
|
|
66
|
+
plain = cipher.update(ciphertext.subarray(0, -16));
|
|
67
|
+
const final = cipher.final();
|
|
68
|
+
const result = Buffer.concat([plain, final]);
|
|
69
|
+
final.fill(0);
|
|
70
|
+
return result;
|
|
71
|
+
}
|
|
72
|
+
catch (cause) {
|
|
73
|
+
throw new agent_state_js_1.AgentStateError("INVALID_ENVELOPE", { cause });
|
|
74
|
+
}
|
|
75
|
+
finally {
|
|
76
|
+
plain?.fill(0);
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
function createSealedAgentStateCodec(providerID, wrapper, expectedAgentID) {
|
|
80
|
+
if (!/^[a-z][a-z0-9]*([.-][a-z0-9]+)*$/.test(providerID) ||
|
|
81
|
+
providerID.length > 64 ||
|
|
82
|
+
!wrapper ||
|
|
83
|
+
typeof wrapper.wrapKey !== "function" ||
|
|
84
|
+
typeof wrapper.unwrapKey !== "function")
|
|
85
|
+
throw new agent_state_js_1.AgentStateError("INVALID_WRAPPER_CONFIG");
|
|
86
|
+
const bindingFor = (agentID) => {
|
|
87
|
+
if (typeof agentID !== "string" ||
|
|
88
|
+
!agentID ||
|
|
89
|
+
agentID.trim() !== agentID ||
|
|
90
|
+
Buffer.byteLength(agentID) > 256 ||
|
|
91
|
+
[...agentID].some((ch) => ch.charCodeAt(0) < 32 || ch.charCodeAt(0) === 127) ||
|
|
92
|
+
(expectedAgentID !== undefined && expectedAgentID !== agentID))
|
|
93
|
+
throw new agent_state_js_1.AgentStateError("ENVELOPE_IDENTITY_MISMATCH");
|
|
94
|
+
return { purpose: PURPOSE, envelopeVersion: 1, providerID, agentID };
|
|
95
|
+
};
|
|
96
|
+
if (expectedAgentID !== undefined)
|
|
97
|
+
bindingFor(expectedAgentID);
|
|
98
|
+
return {
|
|
99
|
+
async encode(state, signal) {
|
|
100
|
+
signal?.throwIfAborted();
|
|
101
|
+
const raw = (0, agent_state_js_1.encodeAgentState)(state);
|
|
102
|
+
const key = (0, node_crypto_1.randomBytes)(32);
|
|
103
|
+
const copy = Buffer.from(key);
|
|
104
|
+
try {
|
|
105
|
+
const binding = bindingFor(state.agent_id);
|
|
106
|
+
const wrapped = wrappedWire(await wrapper.wrapKey(copy, { ...binding }, signal));
|
|
107
|
+
signal?.throwIfAborted();
|
|
108
|
+
const nonce = (0, node_crypto_1.randomBytes)(12);
|
|
109
|
+
const associated = aad(binding, wrapped);
|
|
110
|
+
const cipher = (0, node_crypto_1.createCipheriv)("aes-256-gcm", key, nonce);
|
|
111
|
+
cipher.setAAD(associated);
|
|
112
|
+
const ciphertext = Buffer.concat([cipher.update(raw), cipher.final(), cipher.getAuthTag()]);
|
|
113
|
+
const recovered = await wrapper.unwrapKey({
|
|
114
|
+
version: wrapped.version,
|
|
115
|
+
ciphertext: Buffer.from(wrapped.ciphertext, "base64"),
|
|
116
|
+
...(wrapped.metadata === undefined ? {} : { metadata: wrapped.metadata }),
|
|
117
|
+
}, { ...binding }, signal);
|
|
118
|
+
let check;
|
|
119
|
+
try {
|
|
120
|
+
signal?.throwIfAborted();
|
|
121
|
+
check = decrypt(recovered, nonce, ciphertext, associated);
|
|
122
|
+
if (!check.equals(raw))
|
|
123
|
+
throw new agent_state_js_1.AgentStateError("ENVELOPE_ROUNDTRIP");
|
|
124
|
+
}
|
|
125
|
+
finally {
|
|
126
|
+
check?.fill(0);
|
|
127
|
+
recovered.fill(0);
|
|
128
|
+
}
|
|
129
|
+
const envelope = (0, agent_state_js_1.encodeAgentJSON)({
|
|
130
|
+
version: 1,
|
|
131
|
+
purpose: PURPOSE,
|
|
132
|
+
agent_id: binding.agentID,
|
|
133
|
+
provider_id: providerID,
|
|
134
|
+
wrapped_key: wrapped,
|
|
135
|
+
nonce: nonce.toString("base64"),
|
|
136
|
+
ciphertext: ciphertext.toString("base64"),
|
|
137
|
+
});
|
|
138
|
+
(0, strict_json_js_1.parseStrictJson)(envelope, 2 << 20);
|
|
139
|
+
return envelope;
|
|
140
|
+
}
|
|
141
|
+
finally {
|
|
142
|
+
raw.fill(0);
|
|
143
|
+
key.fill(0);
|
|
144
|
+
copy.fill(0);
|
|
145
|
+
}
|
|
146
|
+
},
|
|
147
|
+
async decode(raw, signal) {
|
|
148
|
+
signal?.throwIfAborted();
|
|
149
|
+
const metadataMembers = new Map();
|
|
150
|
+
const envelope = (0, agent_state_js_1.exactObject)((0, strict_json_js_1.parseStrictJson)(raw, 2 << 20, (object, key, value) => {
|
|
151
|
+
if (key === "metadata")
|
|
152
|
+
metadataMembers.set(object, value);
|
|
153
|
+
}), "version purpose agent_id provider_id wrapped_key nonce ciphertext");
|
|
154
|
+
if (envelope.version !== 1n ||
|
|
155
|
+
envelope.purpose !== PURPOSE ||
|
|
156
|
+
envelope.provider_id !== providerID)
|
|
157
|
+
throw new agent_state_js_1.AgentStateError("INVALID_ENVELOPE");
|
|
158
|
+
const binding = bindingFor(envelope.agent_id);
|
|
159
|
+
const record = (0, agent_state_js_1.exactObject)(envelope.wrapped_key, "version ciphertext metadata");
|
|
160
|
+
if (typeof record.version !== "bigint" ||
|
|
161
|
+
record.version < 1n ||
|
|
162
|
+
record.version > BigInt(Number.MAX_SAFE_INTEGER))
|
|
163
|
+
throw new agent_state_js_1.AgentStateError("INVALID_WRAPPED_KEY");
|
|
164
|
+
const wrapped = {
|
|
165
|
+
version: Number(record.version),
|
|
166
|
+
ciphertext: base64(record.ciphertext, 65536),
|
|
167
|
+
...(record.metadata === undefined ? {} : { metadata: record.metadata }),
|
|
168
|
+
};
|
|
169
|
+
const wire = wrappedWire(wrapped);
|
|
170
|
+
const nonce = base64(envelope.nonce, 12);
|
|
171
|
+
const ciphertext = base64(envelope.ciphertext, (1 << 20) + 16);
|
|
172
|
+
if (nonce.length !== 12 || ciphertext.length < 16)
|
|
173
|
+
throw new agent_state_js_1.AgentStateError("INVALID_ENVELOPE");
|
|
174
|
+
const key = await wrapper.unwrapKey(wrapped, { ...binding }, signal);
|
|
175
|
+
let plaintext;
|
|
176
|
+
try {
|
|
177
|
+
signal?.throwIfAborted();
|
|
178
|
+
plaintext = decrypt(key, nonce, ciphertext, aad(binding, wire, metadataMembers.get(record)));
|
|
179
|
+
const state = (0, agent_state_js_1.decodeAgentState)(plaintext);
|
|
180
|
+
if (state.agent_id !== binding.agentID)
|
|
181
|
+
throw new agent_state_js_1.AgentStateError("ENVELOPE_IDENTITY_MISMATCH");
|
|
182
|
+
return state;
|
|
183
|
+
}
|
|
184
|
+
finally {
|
|
185
|
+
key.fill(0);
|
|
186
|
+
plaintext?.fill(0);
|
|
187
|
+
}
|
|
188
|
+
},
|
|
189
|
+
};
|
|
190
|
+
}
|
|
191
|
+
function openSealedFileAgentState(path, providerID, wrapper, expectedAgentID) {
|
|
192
|
+
return new file_agent_state_js_1.FileAgentState(path, createSealedAgentStateCodec(providerID, wrapper, expectedAgentID));
|
|
193
|
+
}
|
|
@@ -6,7 +6,7 @@ export type StrictJsonValue = null | boolean | number | bigint | string | Strict
|
|
|
6
6
|
* integer ambiguity. NHP uses uint64 session identifiers, so integral values
|
|
7
7
|
* outside Number's safe range remain bigint instead of losing wire bytes.
|
|
8
8
|
*/
|
|
9
|
-
export declare function parseStrictJson(input: Uint8Array, maxBytes: number): StrictJsonValue;
|
|
9
|
+
export declare function parseStrictJson(input: Uint8Array, maxBytes: number, onMember?: (object: object, key: string, raw: string) => void): StrictJsonValue;
|
|
10
10
|
export declare function isStrictJsonObject(value: StrictJsonValue | undefined): value is {
|
|
11
11
|
[key: string]: StrictJsonValue;
|
|
12
12
|
};
|
|
@@ -9,11 +9,11 @@ const JSON_NUMBER = /-?(?:0|[1-9][0-9]*)(?:\.[0-9]+)?(?:[eE][+-]?[0-9]+)?/y;
|
|
|
9
9
|
* integer ambiguity. NHP uses uint64 session identifiers, so integral values
|
|
10
10
|
* outside Number's safe range remain bigint instead of losing wire bytes.
|
|
11
11
|
*/
|
|
12
|
-
function parseStrictJson(input, maxBytes) {
|
|
12
|
+
function parseStrictJson(input, maxBytes, onMember) {
|
|
13
13
|
if (input.byteLength > maxBytes)
|
|
14
14
|
throw new Error(`JSON exceeds ${maxBytes}-byte limit`);
|
|
15
15
|
const text = new TextDecoder("utf-8", { fatal: true }).decode(input);
|
|
16
|
-
const parser = new StrictJsonParser(text);
|
|
16
|
+
const parser = new StrictJsonParser(text, onMember);
|
|
17
17
|
const value = parser.parseValue(0);
|
|
18
18
|
parser.skipWhitespace();
|
|
19
19
|
if (!parser.done)
|
|
@@ -22,9 +22,11 @@ function parseStrictJson(input, maxBytes) {
|
|
|
22
22
|
}
|
|
23
23
|
class StrictJsonParser {
|
|
24
24
|
input;
|
|
25
|
+
onMember;
|
|
25
26
|
#offset = 0;
|
|
26
|
-
constructor(input) {
|
|
27
|
+
constructor(input, onMember) {
|
|
27
28
|
this.input = input;
|
|
29
|
+
this.onMember = onMember;
|
|
28
30
|
}
|
|
29
31
|
get done() {
|
|
30
32
|
return this.#offset === this.input.length;
|
|
@@ -77,7 +79,10 @@ class StrictJsonParser {
|
|
|
77
79
|
this.skipWhitespace();
|
|
78
80
|
if (!this.consume(":"))
|
|
79
81
|
throw new Error("JSON object key has no value");
|
|
82
|
+
this.skipWhitespace();
|
|
83
|
+
const start = this.#offset;
|
|
80
84
|
value[key] = this.parseValue(depth);
|
|
85
|
+
this.onMember?.(value, key, this.input.slice(start, this.#offset));
|
|
81
86
|
this.skipWhitespace();
|
|
82
87
|
if (this.consume("}"))
|
|
83
88
|
return value;
|
package/dist/cjs/node.d.ts
CHANGED
|
@@ -1,3 +1,17 @@
|
|
|
1
1
|
export { createPortalOpener, PortalBusyError, PortalConfigurationError, PortalDenyError, PortalInvalidReplyError, PortalOpenerClosedError, PortalOpenerNotReadyError, PortalOpenerNotStartedError, PortalOpenTimeoutError, PortalRedirectError, PortalStateError, PortalTargetChangedError, PortalTooManyRedirectsError, PortalVerificationError, } from "./node/portal-opener.js";
|
|
2
2
|
export type { CreatePortalOpenerOptions, PortalFetchOptions, PortalOpener, PortalOpenerFailureClass, PortalOpenerHealth, PortalOpenerState, PortalRequestBuilder, PortalStartOptions, } from "./node/portal-opener.js";
|
|
3
3
|
export type { PortalDeployment, PortalDeploymentCell, PortalDeploymentIssuer, } from "./node/deployment.js";
|
|
4
|
+
export { RelayError, validateRelayURL } from "./node/relay.js";
|
|
5
|
+
export { createStaticProvider, createDiscoveryProvider, createHTTPManifestFetcher, } from "./node/provider.js";
|
|
6
|
+
export type { PortalProvider, DiscoveryProviderOptions } from "./node/provider.js";
|
|
7
|
+
export { AgentStateError, decodeAgentState, encodeAgentState } from "./node/agent-state.js";
|
|
8
|
+
export type { AgentState, AgentAssignment, NHPUDPEndpoint, AgentStateStore, AssignmentRegistration, PendingAgentActivation, PendingAgentCompletion, PendingAgentCredentialRecovery, PendingAgentCredentialRecoveryIssue, } from "./node/agent-state.js";
|
|
9
|
+
export type { AgentStateCodec } from "./node/file-agent-state.js";
|
|
10
|
+
export type { AgentExchange, AgentTransport } from "./node/agent-transport.js";
|
|
11
|
+
export { AgentTransportError } from "./node/agent-transport.js";
|
|
12
|
+
export type { NHPMessage } from "./node/nhp-wire.js";
|
|
13
|
+
export { FileAgentState } from "./node/file-agent-state.js";
|
|
14
|
+
export { createSealedAgentStateCodec, openSealedFileAgentState, } from "./node/sealed-agent-state.js";
|
|
15
|
+
export type { AgentStateKeyBinding, WrappedAgentStateKey, AgentStateKeyWrapper, } from "./node/sealed-agent-state.js";
|
|
16
|
+
export { AgentRuntime, AgentLifecycleError, connectAgentRuntime, recoverAgentRuntime, } from "./node/agent-runtime.js";
|
|
17
|
+
export type { AgentRuntimeOptions, AgentRecoveryOptions, AgentOTPChallenge, NativeAgentKnockOptions, NativeAgentGrant, NativeSessionReceipt, } from "./node/agent-runtime.js";
|
package/dist/cjs/node.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.PortalVerificationError = exports.PortalTooManyRedirectsError = exports.PortalTargetChangedError = exports.PortalStateError = exports.PortalRedirectError = exports.PortalOpenTimeoutError = exports.PortalOpenerNotStartedError = exports.PortalOpenerNotReadyError = exports.PortalOpenerClosedError = exports.PortalInvalidReplyError = exports.PortalDenyError = exports.PortalConfigurationError = exports.PortalBusyError = exports.createPortalOpener = void 0;
|
|
3
|
+
exports.recoverAgentRuntime = exports.connectAgentRuntime = exports.AgentLifecycleError = exports.AgentRuntime = exports.openSealedFileAgentState = exports.createSealedAgentStateCodec = exports.FileAgentState = exports.AgentTransportError = exports.encodeAgentState = exports.decodeAgentState = exports.AgentStateError = exports.createHTTPManifestFetcher = exports.createDiscoveryProvider = exports.createStaticProvider = exports.validateRelayURL = exports.RelayError = exports.PortalVerificationError = exports.PortalTooManyRedirectsError = exports.PortalTargetChangedError = exports.PortalStateError = exports.PortalRedirectError = exports.PortalOpenTimeoutError = exports.PortalOpenerNotStartedError = exports.PortalOpenerNotReadyError = exports.PortalOpenerClosedError = exports.PortalInvalidReplyError = exports.PortalDenyError = exports.PortalConfigurationError = exports.PortalBusyError = exports.createPortalOpener = void 0;
|
|
4
4
|
var portal_opener_js_1 = require("./node/portal-opener.js");
|
|
5
5
|
Object.defineProperty(exports, "createPortalOpener", { enumerable: true, get: function () { return portal_opener_js_1.createPortalOpener; } });
|
|
6
6
|
Object.defineProperty(exports, "PortalBusyError", { enumerable: true, get: function () { return portal_opener_js_1.PortalBusyError; } });
|
|
@@ -16,3 +16,26 @@ Object.defineProperty(exports, "PortalStateError", { enumerable: true, get: func
|
|
|
16
16
|
Object.defineProperty(exports, "PortalTargetChangedError", { enumerable: true, get: function () { return portal_opener_js_1.PortalTargetChangedError; } });
|
|
17
17
|
Object.defineProperty(exports, "PortalTooManyRedirectsError", { enumerable: true, get: function () { return portal_opener_js_1.PortalTooManyRedirectsError; } });
|
|
18
18
|
Object.defineProperty(exports, "PortalVerificationError", { enumerable: true, get: function () { return portal_opener_js_1.PortalVerificationError; } });
|
|
19
|
+
var relay_js_1 = require("./node/relay.js");
|
|
20
|
+
Object.defineProperty(exports, "RelayError", { enumerable: true, get: function () { return relay_js_1.RelayError; } });
|
|
21
|
+
Object.defineProperty(exports, "validateRelayURL", { enumerable: true, get: function () { return relay_js_1.validateRelayURL; } });
|
|
22
|
+
var provider_js_1 = require("./node/provider.js");
|
|
23
|
+
Object.defineProperty(exports, "createStaticProvider", { enumerable: true, get: function () { return provider_js_1.createStaticProvider; } });
|
|
24
|
+
Object.defineProperty(exports, "createDiscoveryProvider", { enumerable: true, get: function () { return provider_js_1.createDiscoveryProvider; } });
|
|
25
|
+
Object.defineProperty(exports, "createHTTPManifestFetcher", { enumerable: true, get: function () { return provider_js_1.createHTTPManifestFetcher; } });
|
|
26
|
+
var agent_state_js_1 = require("./node/agent-state.js");
|
|
27
|
+
Object.defineProperty(exports, "AgentStateError", { enumerable: true, get: function () { return agent_state_js_1.AgentStateError; } });
|
|
28
|
+
Object.defineProperty(exports, "decodeAgentState", { enumerable: true, get: function () { return agent_state_js_1.decodeAgentState; } });
|
|
29
|
+
Object.defineProperty(exports, "encodeAgentState", { enumerable: true, get: function () { return agent_state_js_1.encodeAgentState; } });
|
|
30
|
+
var agent_transport_js_1 = require("./node/agent-transport.js");
|
|
31
|
+
Object.defineProperty(exports, "AgentTransportError", { enumerable: true, get: function () { return agent_transport_js_1.AgentTransportError; } });
|
|
32
|
+
var file_agent_state_js_1 = require("./node/file-agent-state.js");
|
|
33
|
+
Object.defineProperty(exports, "FileAgentState", { enumerable: true, get: function () { return file_agent_state_js_1.FileAgentState; } });
|
|
34
|
+
var sealed_agent_state_js_1 = require("./node/sealed-agent-state.js");
|
|
35
|
+
Object.defineProperty(exports, "createSealedAgentStateCodec", { enumerable: true, get: function () { return sealed_agent_state_js_1.createSealedAgentStateCodec; } });
|
|
36
|
+
Object.defineProperty(exports, "openSealedFileAgentState", { enumerable: true, get: function () { return sealed_agent_state_js_1.openSealedFileAgentState; } });
|
|
37
|
+
var agent_runtime_js_1 = require("./node/agent-runtime.js");
|
|
38
|
+
Object.defineProperty(exports, "AgentRuntime", { enumerable: true, get: function () { return agent_runtime_js_1.AgentRuntime; } });
|
|
39
|
+
Object.defineProperty(exports, "AgentLifecycleError", { enumerable: true, get: function () { return agent_runtime_js_1.AgentLifecycleError; } });
|
|
40
|
+
Object.defineProperty(exports, "connectAgentRuntime", { enumerable: true, get: function () { return agent_runtime_js_1.connectAgentRuntime; } });
|
|
41
|
+
Object.defineProperty(exports, "recoverAgentRuntime", { enumerable: true, get: function () { return agent_runtime_js_1.recoverAgentRuntime; } });
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
import { ERROR_CODE_CRID_MISMATCH, ERROR_CODE_INVALID_CRID, ERROR_CODE_INVALID_CRID_KEY, ERROR_CODE_MISSING_CRID, QURLError } from "./errors.js";
|
|
2
|
+
export type CRIDVerificationErrorCode = typeof ERROR_CODE_MISSING_CRID | typeof ERROR_CODE_INVALID_CRID | typeof ERROR_CODE_INVALID_CRID_KEY | typeof ERROR_CODE_CRID_MISMATCH;
|
|
3
|
+
/** A local, fail-closed CRID verification failure. */
|
|
4
|
+
export declare class CRIDVerificationError extends QURLError {
|
|
5
|
+
readonly code: CRIDVerificationErrorCode;
|
|
6
|
+
constructor(code: CRIDVerificationErrorCode, message: string);
|
|
7
|
+
}
|
|
8
|
+
export interface ShareLinkInit {
|
|
9
|
+
link: string;
|
|
10
|
+
qurlId?: string;
|
|
11
|
+
crid?: string;
|
|
12
|
+
type?: string;
|
|
13
|
+
expiresAt?: Date;
|
|
14
|
+
expiresInSeconds?: number;
|
|
15
|
+
singleUse?: boolean;
|
|
16
|
+
}
|
|
17
|
+
/** Credential-redacted, JSON-safe representation returned by {@link ShareLink.toJSON}. */
|
|
18
|
+
export interface ShareLinkJSON {
|
|
19
|
+
link: "[redacted]";
|
|
20
|
+
qurlId?: string;
|
|
21
|
+
crid?: string;
|
|
22
|
+
type?: string;
|
|
23
|
+
expiresAt?: string;
|
|
24
|
+
expiresInSeconds?: number;
|
|
25
|
+
singleUse?: boolean;
|
|
26
|
+
}
|
|
27
|
+
/**
|
|
28
|
+
* A freshly minted, one-time-returned access link for an existing resource.
|
|
29
|
+
* Instances are frozen during construction and are not designed for subclassing.
|
|
30
|
+
*/
|
|
31
|
+
export declare class ShareLink {
|
|
32
|
+
#private;
|
|
33
|
+
/**
|
|
34
|
+
* The one-time secret share link. This property is deliberately
|
|
35
|
+
* non-enumerable: read it directly instead of spreading or cloning the object.
|
|
36
|
+
*/
|
|
37
|
+
readonly link: string;
|
|
38
|
+
/** Token ID when supplied by the service. */
|
|
39
|
+
readonly qurlId?: string;
|
|
40
|
+
readonly crid?: string;
|
|
41
|
+
readonly type?: string;
|
|
42
|
+
readonly expiresInSeconds?: number;
|
|
43
|
+
/** Whether the link is single-use; undefined when the service omits the field. */
|
|
44
|
+
readonly singleUse?: boolean;
|
|
45
|
+
constructor(init: ShareLinkInit);
|
|
46
|
+
/** Effective expiry as a defensive copy; mutating it does not change this share. */
|
|
47
|
+
get expiresAt(): Date | undefined;
|
|
48
|
+
/** Serialize safe metadata while redacting the one-time-returned credential. */
|
|
49
|
+
toJSON(): ShareLinkJSON;
|
|
50
|
+
/**
|
|
51
|
+
* Verify that this response's CRID was derived from a DER SubjectPublicKeyInfo
|
|
52
|
+
* already trusted by the caller. This binds the response CRID to that key; it
|
|
53
|
+
* does not independently prove that the secret link fragment belongs to it.
|
|
54
|
+
*/
|
|
55
|
+
verifyCrid(derSpki: ArrayBuffer | ArrayBufferView): Promise<void>;
|
|
56
|
+
}
|