@floegence/flowersec-core 3.2.1 → 5.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.
Files changed (145) hide show
  1. package/README.md +57 -200
  2. package/THIRD_PARTY_NOTICES.md +11 -11
  3. package/dist/browser/index.d.ts +0 -5
  4. package/dist/browser/index.js +0 -3
  5. package/dist/cli.js +93 -64
  6. package/dist/facade.d.ts +0 -17
  7. package/dist/facade.js +0 -11
  8. package/dist/generated/unicode151.d.ts +2 -0
  9. package/dist/generated/unicode151.js +83 -0
  10. package/dist/interop/proxyServerPeer.js +34 -20
  11. package/dist/interop/serverParityPeer.js +11 -252
  12. package/dist/node/acceptor.d.ts +1 -57
  13. package/dist/node/acceptor.js +3 -420
  14. package/dist/node/acceptorV3.js +2 -2
  15. package/dist/node/connectSessionV3.js +2 -2
  16. package/dist/node/index.d.ts +2 -17
  17. package/dist/node/index.js +1 -9
  18. package/dist/node/nativeTransportAddon.d.ts +7 -36
  19. package/dist/node/nativeTransportAddon.js +7 -27
  20. package/dist/node/proxyServer.d.ts +2 -2
  21. package/dist/node/proxyServer.js +2 -2
  22. package/dist/node/rawQuicAdapterV3.d.ts +2 -2
  23. package/dist/node/rawQuicAdapterV3.js +1 -1
  24. package/dist/node/rawQuicServerV3.d.ts +2 -2
  25. package/dist/node/rawQuicServerV3.js +1 -1
  26. package/dist/node/rawQuicTls.d.ts +2 -0
  27. package/dist/node/{rawQuicAdapter.js → rawQuicTls.js} +30 -45
  28. package/dist/node/tunnelRuntimeV3.js +2 -2
  29. package/dist/proxy/index.d.ts +1 -1
  30. package/dist/proxy/index.js +1 -1
  31. package/dist/proxy/integration.d.ts +5 -5
  32. package/dist/proxy/integration.js +2 -2
  33. package/dist/proxy/runtime.js +11 -26
  34. package/dist/proxy/serviceWorkerRuntime.d.ts +7 -0
  35. package/dist/proxy/serviceWorkerRuntime.js +75 -0
  36. package/dist/proxy/types.d.ts +0 -1
  37. package/dist/proxy/windowBridge.d.ts +2 -0
  38. package/dist/proxy/windowBridge.js +53 -6
  39. package/dist/public/contract.d.ts +1 -3
  40. package/dist/public/streamHandlers.d.ts +0 -3
  41. package/dist/public/streamHandlers.js +2 -4
  42. package/dist/public/streamMetadata.js +2 -2
  43. package/dist/runtime/errors.d.ts +2 -2
  44. package/dist/testSupport/tlsFixture.d.ts +2 -0
  45. package/dist/testSupport/tlsFixture.js +8 -0
  46. package/dist/utils/errors.d.ts +0 -2
  47. package/dist/utils/errors.js +0 -80
  48. package/dist/v3/artifact.js +17 -3
  49. package/dist/v3/carrier.d.ts +2 -2
  50. package/dist/v3/controller.js +2 -6
  51. package/dist/v3/protocol.js +2 -103
  52. package/dist/v3/retryDisposition.d.ts +2 -2
  53. package/dist/v3/retryDisposition.js +0 -7
  54. package/dist/v3/security.d.ts +1 -10
  55. package/dist/v3/security.js +2 -9
  56. package/dist/vendor/tr46.d.ts +4 -0
  57. package/dist/vendor/tr46.js +37 -32
  58. package/package.json +19 -17
  59. package/sbom/cyclonedx.json +62 -62
  60. package/sbom/spdx.json +65 -65
  61. package/dist/browser/connectSession.d.ts +0 -13
  62. package/dist/browser/connectSession.js +0 -40
  63. package/dist/browser/runtimeCapability.d.ts +0 -13
  64. package/dist/browser/runtimeCapability.js +0 -27
  65. package/dist/browser/sessionRuntime.d.ts +0 -2
  66. package/dist/browser/sessionRuntime.js +0 -21
  67. package/dist/browser/v2.d.ts +0 -3
  68. package/dist/browser/v2.js +0 -2
  69. package/dist/browser/webTransportClient.d.ts +0 -8
  70. package/dist/browser/webTransportClient.js +0 -55
  71. package/dist/connectionController.d.ts +0 -55
  72. package/dist/connectionController.js +0 -441
  73. package/dist/connector/adapters/rawQuicCandidate.d.ts +0 -5
  74. package/dist/connector/adapters/rawQuicCandidate.js +0 -71
  75. package/dist/connector/adapters/webSocketCandidate.d.ts +0 -7
  76. package/dist/connector/adapters/webSocketCandidate.js +0 -116
  77. package/dist/connector/adapters/webTransportCandidate.d.ts +0 -5
  78. package/dist/connector/adapters/webTransportCandidate.js +0 -73
  79. package/dist/connector/admissionCommit.d.ts +0 -31
  80. package/dist/connector/admissionCommit.js +0 -239
  81. package/dist/connector/sessionAcceptor.d.ts +0 -38
  82. package/dist/connector/sessionAcceptor.js +0 -158
  83. package/dist/connector/sessionConfig.d.ts +0 -3
  84. package/dist/connector/sessionConfig.js +0 -30
  85. package/dist/connector/sessionConnector.d.ts +0 -47
  86. package/dist/connector/sessionConnector.js +0 -606
  87. package/dist/node/connectSession.d.ts +0 -23
  88. package/dist/node/connectSession.js +0 -93
  89. package/dist/node/controlplane.d.ts +0 -137
  90. package/dist/node/controlplane.js +0 -443
  91. package/dist/node/rawQuicAdapter.d.ts +0 -9
  92. package/dist/node/rawQuicServer.d.ts +0 -24
  93. package/dist/node/rawQuicServer.js +0 -37
  94. package/dist/node/runtimeCapability.d.ts +0 -14
  95. package/dist/node/runtimeCapability.js +0 -24
  96. package/dist/node/sessionRuntime.d.ts +0 -2
  97. package/dist/node/sessionRuntime.js +0 -13
  98. package/dist/node/tunnelRuntime.d.ts +0 -42
  99. package/dist/node/tunnelRuntime.js +0 -559
  100. package/dist/node/v2.d.ts +0 -11
  101. package/dist/node/v2.js +0 -6
  102. package/dist/node/webSocketServer.d.ts +0 -24
  103. package/dist/node/webSocketServer.js +0 -135
  104. package/dist/public/artifact.d.ts +0 -11
  105. package/dist/public/artifact.js +0 -44
  106. package/dist/public/artifactLease.d.ts +0 -10
  107. package/dist/public/artifactLease.js +0 -41
  108. package/dist/public/connectError.d.ts +0 -6
  109. package/dist/public/connectError.js +0 -9
  110. package/dist/transport/webSocketAdapter.d.ts +0 -31
  111. package/dist/transport/webSocketAdapter.js +0 -370
  112. package/dist/transport/webTransportAdapter.d.ts +0 -27
  113. package/dist/transport/webTransportAdapter.js +0 -534
  114. package/dist/v2/admissionError.d.ts +0 -4
  115. package/dist/v2/admissionError.js +0 -8
  116. package/dist/v2/artifact.d.ts +0 -128
  117. package/dist/v2/artifact.js +0 -1131
  118. package/dist/v2/artifactLease.d.ts +0 -1
  119. package/dist/v2/artifactLease.js +0 -7
  120. package/dist/v2/capability.d.ts +0 -29
  121. package/dist/v2/capability.js +0 -190
  122. package/dist/v2/carrier.d.ts +0 -84
  123. package/dist/v2/carrier.js +0 -411
  124. package/dist/v2/contract.d.ts +0 -46
  125. package/dist/v2/contract.js +0 -1
  126. package/dist/v2/handshake.d.ts +0 -80
  127. package/dist/v2/handshake.js +0 -470
  128. package/dist/v2/index.d.ts +0 -14
  129. package/dist/v2/index.js +0 -7
  130. package/dist/v2/opaqueArtifact.d.ts +0 -1
  131. package/dist/v2/opaqueArtifact.js +0 -2
  132. package/dist/v2/protocol.d.ts +0 -99
  133. package/dist/v2/protocol.js +0 -737
  134. package/dist/v2/publicSession.d.ts +0 -1
  135. package/dist/v2/publicSession.js +0 -281
  136. package/dist/v2/retryDisposition.d.ts +0 -16
  137. package/dist/v2/retryDisposition.js +0 -30
  138. package/dist/v2/session.d.ts +0 -310
  139. package/dist/v2/session.js +0 -2127
  140. package/dist/v2/streamLifetimeLedger.d.ts +0 -31
  141. package/dist/v2/streamLifetimeLedger.js +0 -131
  142. package/dist/v2/streamMetadata.d.ts +0 -1
  143. package/dist/v2/streamMetadata.js +0 -7
  144. package/dist/v2/unreliableMessage.d.ts +0 -43
  145. package/dist/v2/unreliableMessage.js +0 -254
@@ -1,93 +0,0 @@
1
- import { detectNodeRuntimeCapabilityV2 } from "./runtimeCapability.js";
2
- import { composeCandidateAttemptFactoryV2, SessionConnectorV2, } from "../connector/sessionConnector.js";
3
- import { createWebSocketCandidateFactoryV2 } from "../connector/adapters/webSocketCandidate.js";
4
- import { createRawQuicCandidateFactoryV2 } from "../connector/adapters/rawQuicCandidate.js";
5
- import { createNodeWsFactory } from "./wsFactory.js";
6
- import { projectSessionV2 } from "../v2/publicSession.js";
7
- import { ConnectError } from "../public/connectError.js";
8
- import { nodeSessionRuntimeV2 } from "./sessionRuntime.js";
9
- import { createConnectionControllerV2, } from "../connectionController.js";
10
- import { createRPCRouter, freezeRPCHandlers, } from "./acceptor.js";
11
- import { createNativeRawQuicDriver, NativeTransportUnavailableError, tryLoadNativeTransportAddon, } from "./nativeTransportAddon.js";
12
- import { createNodeRawQuicClientV2, normalizeCertificateChain } from "./rawQuicAdapter.js";
13
- export function createConnectionController(source, options) {
14
- const normalized = normalizeNodeOptions(options);
15
- const controllerOptions = options.maximumAttempts === undefined
16
- ? {}
17
- : { maximumAttempts: options.maximumAttempts };
18
- return createConnectionControllerV2(source, async (lease, signal) => await connectWithRPCSnapshot(lease, normalized, signal), controllerOptions);
19
- }
20
- export async function connect(lease, options) {
21
- const normalized = normalizeNodeOptions(options);
22
- return await connectWithRPCSnapshot(lease, normalized, options.signal);
23
- }
24
- async function connectWithRPCSnapshot(lease, options, signal) {
25
- const rpcRouter = options.rpcSnapshot === undefined
26
- ? undefined
27
- : createRPCRouter(options.rpcSnapshot);
28
- // WebSocket sessions do not require the optional native raw QUIC addon. Load
29
- // it only when a raw QUIC candidate is actually selected by the connector.
30
- const nativeAddon = tryLoadNativeTransportAddon();
31
- const rawQuicFactory = createRawQuicCandidateFactoryV2(async (candidate, artifact, signal) => {
32
- if (nativeAddon === undefined)
33
- throw new NativeTransportUnavailableError();
34
- if (options.tls?.ca === undefined) {
35
- throw new TypeError("raw QUIC requires explicit trust roots");
36
- }
37
- return await createNodeRawQuicClientV2(createNativeRawQuicDriver(nativeAddon), candidate, artifact, { ca: options.tls.ca }, signal, options.connectTimeoutMs);
38
- });
39
- const origin = options.origin;
40
- let websocketFactory;
41
- if (origin !== undefined) {
42
- const wsFactory = createNodeWsFactory(options.tls);
43
- websocketFactory = createWebSocketCandidateFactoryV2((url, subprotocol) => wsFactory(url, origin, subprotocol));
44
- }
45
- const connector = new SessionConnectorV2(lease, composeCandidateAttemptFactoryV2({
46
- ...(websocketFactory === undefined ? {} : { websocket: websocketFactory }),
47
- raw_quic: rawQuicFactory,
48
- }), {
49
- capability: detectNodeRuntimeCapabilityV2(nativeAddon !== undefined),
50
- runtime: nodeSessionRuntimeV2,
51
- ...(rpcRouter === undefined ? {} : { rpcRouter }),
52
- ...(options.connectTimeoutMs === undefined ? {} : { connectTimeoutMs: options.connectTimeoutMs }),
53
- });
54
- const result = await connector.connect(signal === undefined ? {} : { signal });
55
- return projectSessionV2(result.session);
56
- }
57
- function normalizeNodeOptions(options) {
58
- try {
59
- const origin = normalizeOrigin(options.origin);
60
- if (options.connectTimeoutMs !== undefined &&
61
- (!Number.isSafeInteger(options.connectTimeoutMs) || options.connectTimeoutMs < 1)) {
62
- throw new RangeError("connectTimeoutMs must be a positive safe integer");
63
- }
64
- if (origin !== undefined)
65
- createNodeWsFactory(options.tls);
66
- if (options.tls?.ca !== undefined)
67
- normalizeCertificateChain(options.tls.ca);
68
- return Object.freeze({
69
- ...(origin === undefined ? {} : { origin }),
70
- ...(options.connectTimeoutMs === undefined ? {} : { connectTimeoutMs: options.connectTimeoutMs }),
71
- ...(options.tls === undefined ? {} : { tls: options.tls }),
72
- ...(options.rpcHandlers === undefined ? {} : { rpcSnapshot: freezeRPCHandlers(options.rpcHandlers) }),
73
- });
74
- }
75
- catch {
76
- throw new ConnectError("invalid_options");
77
- }
78
- }
79
- function normalizeOrigin(input) {
80
- if (input === undefined)
81
- return undefined;
82
- let parsed;
83
- try {
84
- parsed = new URL(input);
85
- }
86
- catch {
87
- throw new TypeError("origin must be an absolute HTTP(S) origin");
88
- }
89
- if ((parsed.protocol !== "https:" && parsed.protocol !== "http:") || parsed.origin !== input || parsed.username !== "" || parsed.password !== "") {
90
- throw new TypeError("origin must be an absolute HTTP(S) origin without path, query, or credentials");
91
- }
92
- return parsed.origin;
93
- }
@@ -1,137 +0,0 @@
1
- import { type Artifact } from "../public/artifact.js";
2
- export type ControlPlaneErrorCode = "invalid_input" | "issuance_failed";
3
- export declare class ControlPlaneError extends Error {
4
- readonly code: ControlPlaneErrorCode;
5
- constructor(code: ControlPlaneErrorCode);
6
- }
7
- /** Validated listener URLs used when issuing opaque artifacts. */
8
- export declare class EndpointSet {
9
- #private;
10
- constructor(...urls: readonly string[]);
11
- }
12
- export declare function createEndpointSet(...urls: readonly string[]): EndpointSet;
13
- export type SessionOptions = Readonly<{
14
- channelId: string;
15
- expiresAtUnixSeconds?: number;
16
- idleTimeoutSeconds?: number;
17
- maxInboundStreams?: number;
18
- }>;
19
- export type Scope = Readonly<{
20
- name: string;
21
- version: number;
22
- critical: boolean;
23
- payload: Readonly<Record<string, unknown>>;
24
- }>;
25
- export type ArtifactMetadata = Readonly<{
26
- scopes?: readonly Scope[];
27
- correlationTags?: Readonly<Record<string, string>>;
28
- }>;
29
- export type DirectIssueOptions = Readonly<{
30
- session: SessionOptions;
31
- endpoints: EndpointSet;
32
- rendezvousGroupId: string;
33
- listenerAudience: string;
34
- upstreamAddress: string;
35
- metadata?: ArtifactMetadata;
36
- }>;
37
- export type TunnelIssueOptions = Readonly<{
38
- session: SessionOptions;
39
- endpoints: EndpointSet;
40
- rendezvousGroupId: string;
41
- listenerAudience: string;
42
- firstEndpointId: string;
43
- secondEndpointId: string;
44
- allowReplacement?: boolean;
45
- firstMetadata?: ArtifactMetadata;
46
- secondMetadata?: ArtifactMetadata;
47
- }>;
48
- export declare class Issuer {
49
- #private;
50
- constructor();
51
- issueDirect(options: DirectIssueOptions): IssuedArtifact;
52
- issueTunnelPair(options: TunnelIssueOptions): IssuedTunnelPair;
53
- }
54
- export declare class IssuedArtifact {
55
- #private;
56
- artifactJSON(): Uint8Array;
57
- authorizationRecord(): AuthorizationRecord;
58
- lookupKey(): string;
59
- }
60
- export type IssuedTunnelPair = Readonly<{
61
- first: IssuedArtifact;
62
- second: IssuedArtifact;
63
- }>;
64
- export declare class AuthorizationRecord {
65
- #private;
66
- private constructor();
67
- lookupKey(): string;
68
- encode(): Uint8Array;
69
- static parse(encoded: string | Uint8Array): AuthorizationRecord;
70
- }
71
- export declare class RuntimeAuthorizationRequest {
72
- #private;
73
- private constructor();
74
- static parse(encoded: string | Uint8Array): RuntimeAuthorizationRequest;
75
- lookupKey(): string;
76
- }
77
- export type DirectAuthorizationDecision = Readonly<{
78
- decision: "allow";
79
- artifact: Artifact;
80
- }> | Readonly<{
81
- decision: "reject" | "retry";
82
- reason: string;
83
- }>;
84
- export type TunnelAuthorizationDecision = Readonly<{
85
- decision: "allow";
86
- credentialId: string;
87
- leaseId: string;
88
- expiresAtUnixSeconds: number;
89
- expectedPeerEndpointInstanceId: string;
90
- allowReplacement?: boolean;
91
- }> | Readonly<{
92
- decision: "reject" | "retry";
93
- reason: string;
94
- }>;
95
- type DirectAllowDecision = Extract<DirectAuthorizationDecision, Readonly<{
96
- decision: "allow";
97
- }>>;
98
- type DirectRuntimeAllowDecision = DirectAllowDecision & Readonly<{
99
- leaseId: string;
100
- expiresAtUnixSeconds: number;
101
- }>;
102
- type DirectDenyDecision = Extract<DirectAuthorizationDecision, Readonly<{
103
- decision: "reject" | "retry";
104
- }>>;
105
- type TunnelAllowDecision = Extract<TunnelAuthorizationDecision, Readonly<{
106
- decision: "allow";
107
- }>>;
108
- type TunnelDenyDecision = Extract<TunnelAuthorizationDecision, Readonly<{
109
- decision: "reject" | "retry";
110
- }>>;
111
- export declare class AuthorizationResponse<Decision extends DirectAuthorizationDecision = DirectAuthorizationDecision> {
112
- #private;
113
- readonly decision: Decision["decision"];
114
- readonly artifact: Decision extends DirectAllowDecision ? Artifact : undefined;
115
- readonly leaseId: Decision extends DirectRuntimeAllowDecision ? string : undefined;
116
- readonly expiresAtUnixSeconds: Decision extends DirectRuntimeAllowDecision ? number : undefined;
117
- readonly reason: Decision extends DirectDenyDecision ? string : undefined;
118
- json(): Uint8Array;
119
- }
120
- export declare class TunnelAuthorizationResponse<Decision extends TunnelAuthorizationDecision = TunnelAuthorizationDecision> {
121
- #private;
122
- readonly decision: Decision["decision"];
123
- readonly credentialId: Decision extends TunnelAllowDecision ? string : undefined;
124
- readonly leaseId: Decision extends TunnelAllowDecision ? string : undefined;
125
- readonly expiresAtUnixSeconds: Decision extends TunnelAllowDecision ? number : undefined;
126
- readonly expectedPeerEndpointInstanceId: Decision extends TunnelAllowDecision ? string : undefined;
127
- readonly allowReplacement: Decision extends TunnelAllowDecision ? boolean | undefined : undefined;
128
- readonly reason: Decision extends TunnelDenyDecision ? string : undefined;
129
- json(): Uint8Array;
130
- }
131
- export declare function authorizeRuntime(request: RuntimeAuthorizationRequest, record: AuthorizationRecord, leaseId: string, nowUnixSeconds?: number): AuthorizationResponse<DirectRuntimeAllowDecision>;
132
- export declare function authorizeTunnelRuntime(request: RuntimeAuthorizationRequest, record: AuthorizationRecord, leaseId: string, nowUnixSeconds?: number): TunnelAuthorizationResponse<TunnelAllowDecision>;
133
- export declare function rejectRuntime(reason: string, retryable: boolean): AuthorizationResponse<DirectDenyDecision>;
134
- export declare function rejectTunnelRuntime(reason: string, retryable: boolean): TunnelAuthorizationResponse<TunnelDenyDecision>;
135
- export declare function parseAuthorizationRecord(encoded: string | Uint8Array): AuthorizationRecord;
136
- export declare function parseRuntimeAuthorizationRequest(encoded: string | Uint8Array): RuntimeAuthorizationRequest;
137
- export {};
@@ -1,443 +0,0 @@
1
- import { randomBytes as nodeRandomBytes, timingSafeEqual } from "node:crypto";
2
- import { buildFSB2RequestV2, canonicalizeCandidatesV2, computeSessionContractHashV2, decodeArtifactV2JSON, decodeFSB2RequestV2, encodeArtifactV2JSON, encodeFSB2RequestV2, } from "../v2/artifact.js";
3
- import { wrapArtifact } from "../public/artifact.js";
4
- import { base64urlDecode, base64urlEncode } from "../utils/base64url.js";
5
- import { sha256 } from "@noble/hashes/sha2.js";
6
- const MAX_RECORD_BYTES = 96 * 1024;
7
- const LEASE_ID = /^[A-Za-z0-9._~-]{1,128}$/u;
8
- const REASON = /^[a-z][a-z0-9_]*$/u;
9
- const PROFILE = "flowersec/2";
10
- export class ControlPlaneError extends Error {
11
- code;
12
- constructor(code) {
13
- super(`Flowersec control-plane failed (code=${code})`);
14
- this.code = code;
15
- this.name = "ControlPlaneError";
16
- }
17
- }
18
- /** Validated listener URLs used when issuing opaque artifacts. */
19
- export class EndpointSet {
20
- #endpoints;
21
- constructor(...urls) {
22
- if (urls.length < 1 || urls.length > 4)
23
- throw invalidInput();
24
- const values = [];
25
- for (const url of urls) {
26
- if (url.length === 0 || url.trim() !== url)
27
- throw invalidInput();
28
- const separator = url.indexOf("://");
29
- if (separator <= 0)
30
- throw invalidInput();
31
- const scheme = url.slice(0, separator).toLowerCase();
32
- const carrier = scheme === "ws" || scheme === "wss"
33
- ? "websocket"
34
- : scheme === "quic"
35
- ? "raw_quic"
36
- : scheme === "https"
37
- ? "webtransport"
38
- : undefined;
39
- if (carrier === undefined)
40
- throw invalidInput();
41
- values.push({ carrier, url });
42
- }
43
- this.#endpoints = Object.freeze(values);
44
- }
45
- /** @internal */
46
- toCandidates(kind) {
47
- const counts = new Map();
48
- const candidates = this.#endpoints.map(({ carrier, url }) => {
49
- const count = (counts.get(carrier) ?? 0) + 1;
50
- counts.set(carrier, count);
51
- const base = carrier === "websocket" ? "websocket" : carrier === "raw_quic" ? "raw-quic" : "webtransport";
52
- return {
53
- id: count === 1 ? base : `${base}-${count}`,
54
- carrier,
55
- url,
56
- wire_profile: `flowersec-${kind}/2`,
57
- };
58
- });
59
- try {
60
- canonicalizeCandidatesV2(kind, candidates);
61
- }
62
- catch {
63
- throw invalidInput();
64
- }
65
- return candidates;
66
- }
67
- }
68
- export function createEndpointSet(...urls) {
69
- return new EndpointSet(...urls);
70
- }
71
- export class Issuer {
72
- #now;
73
- #random;
74
- constructor() {
75
- this.#now = () => Math.floor(Date.now() / 1000);
76
- this.#random = (size) => nodeRandomBytes(size);
77
- }
78
- issueDirect(options) {
79
- const session = this.#session(options.session);
80
- const candidates = options.endpoints.toCandidates("direct");
81
- if (!validTCPAddress(options.upstreamAddress))
82
- throw invalidInput();
83
- const credential = this.#credential();
84
- const artifact = {
85
- v: 2, profile: PROFILE, session,
86
- path: {
87
- kind: "direct",
88
- rendezvous_group_id: options.rendezvousGroupId,
89
- listener_audience: options.listenerAudience,
90
- routing_token: credential,
91
- candidates,
92
- },
93
- scoped: metadataScopes(options.metadata),
94
- correlation: metadataCorrelation(options.metadata),
95
- };
96
- return this.#issued(artifact, options.upstreamAddress, false);
97
- }
98
- issueTunnelPair(options) {
99
- const session = this.#session(options.session);
100
- const candidates = options.endpoints.toCandidates("tunnel");
101
- if (!validID(options.firstEndpointId) || !validID(options.secondEndpointId) || options.firstEndpointId === options.secondEndpointId)
102
- throw invalidInput();
103
- const allowReplacement = options.allowReplacement ?? false;
104
- const build = (role, local, peer, token, metadata) => this.#issued({
105
- v: 2, profile: PROFILE, session,
106
- path: {
107
- kind: "tunnel", rendezvous_group_id: options.rendezvousGroupId,
108
- listener_audience: options.listenerAudience, role,
109
- local_endpoint_instance_id: local,
110
- expected_peer_endpoint_instance_id: peer,
111
- token, candidates,
112
- },
113
- scoped: metadataScopes(metadata), correlation: metadataCorrelation(metadata),
114
- }, "", allowReplacement);
115
- return {
116
- first: build(1, options.firstEndpointId, options.secondEndpointId, this.#credential(), options.firstMetadata),
117
- second: build(2, options.secondEndpointId, options.firstEndpointId, this.#credential(), options.secondMetadata),
118
- };
119
- }
120
- #session(options) {
121
- const now = this.#now();
122
- const expires = options.expiresAtUnixSeconds ?? now + 60;
123
- const idle = options.idleTimeoutSeconds ?? 60;
124
- const max = options.maxInboundStreams ?? 32;
125
- if (!validID(options.channelId) || !Number.isSafeInteger(expires) || expires <= now || expires > now + 300 ||
126
- !Number.isSafeInteger(idle) || idle < 0 || idle > 0xffff_ffff || !Number.isSafeInteger(max) || max < 1 || max > 128)
127
- throw invalidInput();
128
- const psk = this.#random(32);
129
- if (psk.length !== 32)
130
- throw issuanceFailed();
131
- const unsigned = {
132
- channel_id: options.channelId, init_expire_at_unix_s: expires,
133
- idle_timeout_seconds: idle, establish_timeout_seconds: 30,
134
- rekey_prepare_timeout_seconds: 10, rekey_completion_timeout_seconds: 30,
135
- max_inbound_streams: max, e2ee_psk_b64u: base64urlEncode(psk),
136
- allowed_suites: [1, 2], default_suite: 1, selected_features: 0,
137
- };
138
- const hash = computeSessionContractHashV2({ ...unsigned, contract_hash_b64u: "A".repeat(43) }).hashBase64URL;
139
- return { ...unsigned, contract_hash_b64u: hash };
140
- }
141
- #credential() {
142
- const value = this.#random(32);
143
- if (value.length !== 32)
144
- throw issuanceFailed();
145
- return base64urlEncode(value);
146
- }
147
- #issued(artifact, directUpstream, allowReplacement) {
148
- try {
149
- const bytes = encodeArtifactV2JSON(artifact);
150
- return new IssuedArtifact(bytes, AuthorizationRecord.create(bytes, artifact, directUpstream, allowReplacement));
151
- }
152
- catch (error) {
153
- if (error instanceof ControlPlaneError)
154
- throw error;
155
- throw invalidInput();
156
- }
157
- }
158
- }
159
- export class IssuedArtifact {
160
- #artifact;
161
- #record;
162
- /** @internal */
163
- constructor(artifact, record) {
164
- this.#artifact = artifact.slice();
165
- this.#record = record;
166
- }
167
- artifactJSON() { return this.#artifact.slice(); }
168
- authorizationRecord() { return this.#record; }
169
- lookupKey() { return this.#record.lookupKey(); }
170
- }
171
- export class AuthorizationRecord {
172
- #artifact;
173
- #artifactJSON;
174
- #lookup;
175
- #upstream;
176
- #allowReplacement;
177
- constructor(artifactJSON, artifact, upstream, allowReplacement) {
178
- const credential = artifact.path.kind === "direct" ? artifact.path.routing_token : artifact.path.token;
179
- if (artifact.path.kind === "direct" ? upstream === "" || allowReplacement : upstream !== "")
180
- throw invalidInput();
181
- this.#artifact = artifact;
182
- this.#artifactJSON = artifactJSON.slice();
183
- this.#lookup = base64urlEncode(sha256(new TextEncoder().encode(credential)));
184
- this.#upstream = upstream;
185
- this.#allowReplacement = allowReplacement;
186
- }
187
- /** @internal */
188
- static create(artifactJSON, artifact, upstream, allowReplacement) {
189
- return new AuthorizationRecord(artifactJSON, artifact, upstream, allowReplacement);
190
- }
191
- lookupKey() { return this.#lookup; }
192
- encode() {
193
- const wire = {
194
- schema_version: 1,
195
- artifact_base64url: base64urlEncode(this.#artifactJSON),
196
- lookup_key: this.#lookup,
197
- ...(this.#upstream === "" ? {} : { direct_upstream: this.#upstream }),
198
- allow_replacement: this.#allowReplacement,
199
- };
200
- return new TextEncoder().encode(JSON.stringify(wire));
201
- }
202
- static parse(encoded) {
203
- const bytes = typeof encoded === "string" ? new TextEncoder().encode(encoded) : encoded;
204
- if (bytes.length === 0 || bytes.length > MAX_RECORD_BYTES)
205
- throw invalidInput();
206
- const wire = strictObject(bytes);
207
- assertExactKeys(wire, ["schema_version", "artifact_base64url", "lookup_key", "direct_upstream", "allow_replacement"], ["direct_upstream"]);
208
- if (wire.schema_version !== 1 || typeof wire.artifact_base64url !== "string" || typeof wire.lookup_key !== "string" || typeof wire.allow_replacement !== "boolean")
209
- throw invalidInput();
210
- const artifactJSON = base64urlDecode(wire.artifact_base64url);
211
- const artifact = decodeArtifactV2JSON(artifactJSON);
212
- const upstream = wire.direct_upstream === undefined ? "" : wire.direct_upstream;
213
- if (typeof upstream !== "string")
214
- throw invalidInput();
215
- const record = AuthorizationRecord.create(artifactJSON, artifact, upstream, wire.allow_replacement);
216
- if (record.lookupKey() !== wire.lookup_key)
217
- throw invalidInput();
218
- return record;
219
- }
220
- /** @internal */
221
- get artifact() { return this.#artifact; }
222
- /** @internal */
223
- get artifactJSON() { return this.#artifactJSON.slice(); }
224
- /** @internal */
225
- get upstream() { return this.#upstream; }
226
- /** @internal */
227
- get allowReplacement() { return this.#allowReplacement; }
228
- }
229
- export class RuntimeAuthorizationRequest {
230
- #decoded;
231
- #lookup;
232
- #carrier;
233
- constructor(decoded, carrier) {
234
- this.#decoded = decoded;
235
- this.#carrier = carrier;
236
- const credential = decoded.request.pathKind === "direct" ? decoded.request.routing_token : decoded.request.attach_token;
237
- this.#lookup = base64urlEncode(sha256(new TextEncoder().encode(credential)));
238
- }
239
- /** @internal */
240
- static fromDecoded(decoded, carrier) {
241
- return new RuntimeAuthorizationRequest(decoded, carrier);
242
- }
243
- static parse(encoded) {
244
- const wire = strictObject(typeof encoded === "string" ? new TextEncoder().encode(encoded) : encoded);
245
- assertExactKeys(wire, ["fsb2_base64url", "carrier", "remote_address"]);
246
- if (typeof wire.fsb2_base64url !== "string" || typeof wire.carrier !== "string" || typeof wire.remote_address !== "string" || !validObservedText(wire.remote_address))
247
- throw invalidInput();
248
- const decoded = decodeFSB2RequestV2(base64urlDecode(wire.fsb2_base64url));
249
- const candidate = decoded.request.candidates.find((item) => item.id === decoded.request.chosen_candidate_id);
250
- if (candidate === undefined || candidate.carrier !== wire.carrier)
251
- throw invalidInput();
252
- return new RuntimeAuthorizationRequest(decoded, candidate.carrier);
253
- }
254
- lookupKey() { return this.#lookup; }
255
- /** @internal */ get decoded() { return this.#decoded; }
256
- /** @internal */ get carrier() { return this.#carrier; }
257
- }
258
- /** @internal */
259
- export function runtimeAuthorizationRequestFromDecoded(decoded) {
260
- const candidate = decoded.request.candidates.find((item) => item.id === decoded.request.chosen_candidate_id);
261
- if (candidate === undefined)
262
- throw invalidInput();
263
- return RuntimeAuthorizationRequest.fromDecoded(decoded, candidate.carrier);
264
- }
265
- export class AuthorizationResponse {
266
- #json;
267
- #decision;
268
- decision;
269
- artifact;
270
- leaseId;
271
- expiresAtUnixSeconds;
272
- reason;
273
- /** @internal */
274
- constructor(json, decision) {
275
- this.#json = json.slice();
276
- this.#decision = decision;
277
- this.decision = decision.decision;
278
- this.artifact = (decision.decision === "allow" ? decision.artifact : undefined);
279
- this.leaseId = (decision.decision === "allow" && "leaseId" in decision ? decision.leaseId : undefined);
280
- this.expiresAtUnixSeconds = (decision.decision === "allow" && "expiresAtUnixSeconds" in decision ? decision.expiresAtUnixSeconds : undefined);
281
- this.reason = (decision.decision === "allow" ? undefined : decision.reason);
282
- }
283
- json() { return this.#json.slice(); }
284
- /** @internal */
285
- asDecision() { return this.#decision; }
286
- }
287
- export class TunnelAuthorizationResponse {
288
- #json;
289
- #decision;
290
- decision;
291
- credentialId;
292
- leaseId;
293
- expiresAtUnixSeconds;
294
- expectedPeerEndpointInstanceId;
295
- allowReplacement;
296
- reason;
297
- /** @internal */
298
- constructor(json, decision) {
299
- this.#json = json.slice();
300
- this.#decision = decision;
301
- this.decision = decision.decision;
302
- this.credentialId = (decision.decision === "allow" ? decision.credentialId : undefined);
303
- this.leaseId = (decision.decision === "allow" ? decision.leaseId : undefined);
304
- this.expiresAtUnixSeconds = (decision.decision === "allow" ? decision.expiresAtUnixSeconds : undefined);
305
- this.expectedPeerEndpointInstanceId = (decision.decision === "allow" ? decision.expectedPeerEndpointInstanceId : undefined);
306
- this.allowReplacement = (decision.decision === "allow" ? decision.allowReplacement : undefined);
307
- this.reason = (decision.decision === "allow" ? undefined : decision.reason);
308
- }
309
- json() { return this.#json.slice(); }
310
- /** @internal */
311
- asDecision() { return this.#decision; }
312
- }
313
- export function authorizeRuntime(request, record, leaseId, nowUnixSeconds = Math.floor(Date.now() / 1000)) {
314
- if (!(request instanceof RuntimeAuthorizationRequest) || !(record instanceof AuthorizationRecord) || !LEASE_ID.test(leaseId) || request.lookupKey() !== record.lookupKey())
315
- throw invalidInput();
316
- const artifact = record.artifact;
317
- if (nowUnixSeconds >= artifact.session.init_expire_at_unix_s)
318
- throw invalidInput();
319
- const expected = encodeFSB2RequestV2(buildFSB2RequestV2(artifact, request.decoded.request.chosen_candidate_id));
320
- if (!bytesEqual(expected, request.decoded.raw))
321
- throw invalidInput();
322
- const base = { decision: "allow", credential_id: record.lookupKey(), lease_id: leaseId, expires_at: new Date(artifact.session.init_expire_at_unix_s * 1000).toISOString() };
323
- if (artifact.path.kind !== "direct")
324
- throw invalidInput();
325
- base.direct = { session: sessionWire(artifact.session), upstream: { network: "tcp", address: record.upstream } };
326
- return new AuthorizationResponse(new TextEncoder().encode(JSON.stringify(base)), {
327
- decision: "allow",
328
- artifact: wrapArtifact(artifact),
329
- leaseId,
330
- expiresAtUnixSeconds: artifact.session.init_expire_at_unix_s,
331
- });
332
- }
333
- export function authorizeTunnelRuntime(request, record, leaseId, nowUnixSeconds = Math.floor(Date.now() / 1000)) {
334
- if (!(request instanceof RuntimeAuthorizationRequest) || !(record instanceof AuthorizationRecord) || !LEASE_ID.test(leaseId) || request.lookupKey() !== record.lookupKey())
335
- throw invalidInput();
336
- const artifact = record.artifact;
337
- if (artifact.path.kind !== "tunnel" || nowUnixSeconds >= artifact.session.init_expire_at_unix_s)
338
- throw invalidInput();
339
- const expected = encodeFSB2RequestV2(buildFSB2RequestV2(artifact, request.decoded.request.chosen_candidate_id));
340
- if (!bytesEqual(expected, request.decoded.raw))
341
- throw invalidInput();
342
- const decision = {
343
- decision: "allow",
344
- credentialId: record.lookupKey(),
345
- leaseId,
346
- expiresAtUnixSeconds: artifact.session.init_expire_at_unix_s,
347
- expectedPeerEndpointInstanceId: artifact.path.expected_peer_endpoint_instance_id,
348
- allowReplacement: record.allowReplacement,
349
- };
350
- return new TunnelAuthorizationResponse(new TextEncoder().encode(JSON.stringify({
351
- decision: "allow",
352
- credential_id: decision.credentialId,
353
- lease_id: decision.leaseId,
354
- expires_at: new Date(decision.expiresAtUnixSeconds * 1000).toISOString(),
355
- expected_peer_endpoint_instance_id: decision.expectedPeerEndpointInstanceId,
356
- allow_replacement: decision.allowReplacement,
357
- })), decision);
358
- }
359
- export function rejectRuntime(reason, retryable) {
360
- if (!REASON.test(reason) || reason.length > 64)
361
- throw invalidInput();
362
- const decision = { decision: retryable ? "retry" : "reject", reason };
363
- return new AuthorizationResponse(new TextEncoder().encode(JSON.stringify(decision)), decision);
364
- }
365
- export function rejectTunnelRuntime(reason, retryable) {
366
- if (!REASON.test(reason) || reason.length > 64)
367
- throw invalidInput();
368
- const decision = { decision: retryable ? "retry" : "reject", reason };
369
- return new TunnelAuthorizationResponse(new TextEncoder().encode(JSON.stringify(decision)), decision);
370
- }
371
- export function parseAuthorizationRecord(encoded) { return AuthorizationRecord.parse(encoded); }
372
- export function parseRuntimeAuthorizationRequest(encoded) { return RuntimeAuthorizationRequest.parse(encoded); }
373
- function sessionWire(session) {
374
- return {
375
- channel_id: session.channel_id, init_expire_at_unix_seconds: session.init_expire_at_unix_s,
376
- idle_timeout_seconds: session.idle_timeout_seconds, establish_timeout_seconds: session.establish_timeout_seconds,
377
- rekey_prepare_timeout_seconds: session.rekey_prepare_timeout_seconds,
378
- rekey_completion_timeout_seconds: session.rekey_completion_timeout_seconds,
379
- max_inbound_streams: session.max_inbound_streams, e2ee_psk_base64url: session.e2ee_psk_b64u,
380
- allowed_suites: session.allowed_suites, default_suite: session.default_suite, selected_features: session.selected_features,
381
- };
382
- }
383
- function metadataScopes(metadata) {
384
- return (metadata?.scopes ?? []).map((scope) => ({ scope: scope.name, scope_version: scope.version, critical: scope.critical, payload: scope.payload }));
385
- }
386
- function metadataCorrelation(metadata) {
387
- const tags = Object.entries(metadata?.correlationTags ?? {}).sort(([a], [b]) => a.localeCompare(b)).map(([key, value]) => ({ key, value }));
388
- return { v: 2, tags };
389
- }
390
- function validID(value) { return /^[A-Za-z0-9._~-]{1,128}$/u.test(value); }
391
- function validTCPAddress(value) {
392
- if (value.length === 0 || /[\s/]/u.test(value))
393
- return false;
394
- let host;
395
- let portText;
396
- if (value.startsWith("[")) {
397
- const closing = value.indexOf("]");
398
- if (closing < 2 || value[closing + 1] !== ":")
399
- return false;
400
- host = value.slice(1, closing);
401
- portText = value.slice(closing + 2);
402
- }
403
- else {
404
- const separator = value.lastIndexOf(":");
405
- if (separator <= 0 || value.indexOf(":") !== separator)
406
- return false;
407
- host = value.slice(0, separator);
408
- portText = value.slice(separator + 1);
409
- }
410
- if (host.length === 0 || !/^\d{1,5}$/u.test(portText))
411
- return false;
412
- const port = Number(portText);
413
- return port >= 1 && port <= 65_535;
414
- }
415
- function validObservedText(value) { return value.length > 0 && value.length <= 512 && !/[\u0000-\u001f\u007f]/u.test(value); }
416
- function bytesEqual(a, b) {
417
- if (a.length !== b.length)
418
- return false;
419
- return timingSafeEqual(a, b);
420
- }
421
- function strictObject(bytes) {
422
- let value;
423
- try {
424
- value = JSON.parse(new TextDecoder("utf-8", { fatal: true }).decode(bytes));
425
- }
426
- catch {
427
- throw invalidInput();
428
- }
429
- if (value === null || typeof value !== "object" || Array.isArray(value))
430
- throw invalidInput();
431
- return value;
432
- }
433
- function assertExactKeys(value, allowed, optional = []) {
434
- const allowedSet = new Set(allowed);
435
- for (const key of Object.keys(value))
436
- if (!allowedSet.has(key))
437
- throw invalidInput();
438
- for (const key of allowed)
439
- if (!optional.includes(key) && !(key in value))
440
- throw invalidInput();
441
- }
442
- function invalidInput() { return new ControlPlaneError("invalid_input"); }
443
- function issuanceFailed() { return new ControlPlaneError("issuance_failed"); }
@@ -1,9 +0,0 @@
1
- import type { ArtifactV2, CanonicalArtifactCandidateV2 } from "../v2/artifact.js";
2
- import type { NativeCarrierSessionV2 } from "../v2/carrier.js";
3
- import type { NativeRawQuicDriver } from "./nativeTransportAddon.js";
4
- export type RawQuicTLSOptions = Readonly<{
5
- ca: string | Uint8Array | readonly (string | Uint8Array)[];
6
- }>;
7
- export declare function createNodeRawQuicClientV2(driver: NativeRawQuicDriver, candidate: CanonicalArtifactCandidateV2, artifact: ArtifactV2, tls: RawQuicTLSOptions, signal: AbortSignal, handshakeTimeoutMs?: number): Promise<NativeCarrierSessionV2>;
8
- export declare function normalizeCertificateChain(input: string | Uint8Array | readonly (string | Uint8Array)[]): readonly Uint8Array[];
9
- export declare function normalizePrivateKey(input: string | Uint8Array): Uint8Array;