@actana/sdk 0.3.3 → 0.4.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/README.md +2 -1
- package/dist/core-client.d.ts +18 -0
- package/dist/core-client.d.ts.map +1 -1
- package/dist/core-client.js +26 -2
- package/dist/core-client.js.map +1 -1
- package/dist/core-link-frames.d.ts +77 -1
- package/dist/core-link-frames.d.ts.map +1 -1
- package/dist/core-link-frames.js +37 -1
- package/dist/core-link-frames.js.map +1 -1
- package/dist/core-pairing-csr.d.ts +21 -0
- package/dist/core-pairing-csr.d.ts.map +1 -0
- package/dist/core-pairing-csr.js +159 -0
- package/dist/core-pairing-csr.js.map +1 -0
- package/dist/core-pairing-wire.d.ts +63 -0
- package/dist/core-pairing-wire.d.ts.map +1 -0
- package/dist/core-pairing-wire.js +22 -0
- package/dist/core-pairing-wire.js.map +1 -0
- package/dist/core-pairing.d.ts +211 -0
- package/dist/core-pairing.d.ts.map +1 -0
- package/dist/core-pairing.js +630 -0
- package/dist/core-pairing.js.map +1 -0
- package/dist/core-session.d.ts +213 -5
- package/dist/core-session.d.ts.map +1 -1
- package/dist/core-session.js +326 -28
- package/dist/core-session.js.map +1 -1
- package/package.json +1 -1
|
@@ -0,0 +1,159 @@
|
|
|
1
|
+
// The client half of pairing that never leaves the machine: a key pair, and the
|
|
2
|
+
// certificate signing request that proves possession of it (#280, #284).
|
|
3
|
+
//
|
|
4
|
+
// A pairing exchange hands a Core a *public* key and gets a certificate back.
|
|
5
|
+
// This module is where the other half stays. Everything here runs before a
|
|
6
|
+
// socket is opened, and the only value that ever crosses one is
|
|
7
|
+
// {@link ClientCsr.csrPem} — the private key is returned to the caller, put
|
|
8
|
+
// into the resulting `CoreRegistrationBlob`, and never given to
|
|
9
|
+
// `core-pairing.ts` in a form it could serialise.
|
|
10
|
+
//
|
|
11
|
+
// **Why a hand-written PKCS#10 encoder rather than a library.** `node:crypto`
|
|
12
|
+
// generates key pairs and signs, and it exports a public key as a DER
|
|
13
|
+
// `SubjectPublicKeyInfo` — which is the only structure in a CSR that is hard to
|
|
14
|
+
// produce. What is left is four fields in a fixed order (RFC 2986 §4), so the
|
|
15
|
+
// choice was a ~90-line encoder against a new dependency on the one package in
|
|
16
|
+
// this repository whose dependency list is a reviewed artifact
|
|
17
|
+
// (`__tests__/package-boundaries.test.ts`, and ADR 0025's "a private one is a
|
|
18
|
+
// broken install"). The encoder is checked where it counts: the suite posts
|
|
19
|
+
// what it produces at the real Core, whose `assertSignableCsr` parses it with
|
|
20
|
+
// `@peculiar/x509` and verifies the signature before signing. A CSR this file
|
|
21
|
+
// got wrong fails that test rather than shipping.
|
|
22
|
+
//
|
|
23
|
+
// **RSA-2048, matching the Core.** `@actana/shared/core-cert-material` refuses
|
|
24
|
+
// anything under 2048 bits (`MIN_RSA_MODULUS_BITS`), and its own
|
|
25
|
+
// `generateClientCsr` mints exactly this. A client that asked for something
|
|
26
|
+
// more exotic would be betting on the CA's parser rather than on the algorithm
|
|
27
|
+
// the CA is known to sign.
|
|
28
|
+
import { generateKeyPair, sign } from "node:crypto";
|
|
29
|
+
/** Modulus size. See the header: what the Core's CA is known to sign. */
|
|
30
|
+
const RSA_MODULUS_BITS = 2048;
|
|
31
|
+
/** Longest common name written into the request. The Core caps its own at 48. */
|
|
32
|
+
const MAX_COMMON_NAME = 48;
|
|
33
|
+
/** The common name used when a caller offers nothing usable. */
|
|
34
|
+
const FALLBACK_COMMON_NAME = "actana-client";
|
|
35
|
+
/**
|
|
36
|
+
* Mint a key pair and a CSR naming `commonName`.
|
|
37
|
+
*
|
|
38
|
+
* The subject here is a *request*, and the Core does not honour it: the
|
|
39
|
+
* certificate is issued for the label the operator typed when they opened the
|
|
40
|
+
* pairing session (`core-pairing-routes.ts`, "the subject is the Core's to
|
|
41
|
+
* write"). It is filled in anyway because a CSR with an empty subject is a
|
|
42
|
+
* worse artifact to debug than one that says which machine asked.
|
|
43
|
+
*/
|
|
44
|
+
export async function generateClientCsr(commonName) {
|
|
45
|
+
const { publicKey, privateKey } = await new Promise((resolve, reject) => {
|
|
46
|
+
generateKeyPair("rsa", { modulusLength: RSA_MODULUS_BITS }, (err, publicKey, privateKey) => {
|
|
47
|
+
if (err)
|
|
48
|
+
reject(err);
|
|
49
|
+
else
|
|
50
|
+
resolve({ publicKey, privateKey });
|
|
51
|
+
});
|
|
52
|
+
});
|
|
53
|
+
const spki = new Uint8Array(publicKey.export({ type: "spki", format: "der" }));
|
|
54
|
+
const info = derSequence(DER_VERSION_V1, derName(certificationRequestName(commonName)), spki,
|
|
55
|
+
// `attributes [0] IMPLICIT SET OF Attribute`, empty. Not optional in RFC
|
|
56
|
+
// 2986 — a request with the field left out is one some parsers reject —
|
|
57
|
+
// and nothing this client asks for belongs in it: requested extensions are
|
|
58
|
+
// exactly what the Core refuses to honour.
|
|
59
|
+
DER_EMPTY_ATTRIBUTES);
|
|
60
|
+
// RSASSA-PKCS1-v1_5 over SHA-256, which is what `sign` produces for an RSA
|
|
61
|
+
// key with no `padding` given, and what the algorithm identifier below says.
|
|
62
|
+
const signature = new Uint8Array(sign("sha256", info, privateKey));
|
|
63
|
+
const csr = derSequence(info, DER_SHA256_WITH_RSA, derBitString(signature));
|
|
64
|
+
return {
|
|
65
|
+
csrPem: derToPem("CERTIFICATE REQUEST", csr),
|
|
66
|
+
privateKeyPem: privateKey.export({ type: "pkcs8", format: "pem" }).toString(),
|
|
67
|
+
};
|
|
68
|
+
}
|
|
69
|
+
/**
|
|
70
|
+
* A common name as it can appear in a distinguished name.
|
|
71
|
+
*
|
|
72
|
+
* Same conservative set the Core applies to the label it issues for, for the
|
|
73
|
+
* same reason: an X.509 name is structure, `,` and `=` and `+` are part of it,
|
|
74
|
+
* and a CSR is not the place to find out whose escaping is right.
|
|
75
|
+
*/
|
|
76
|
+
function certificationRequestName(label) {
|
|
77
|
+
const cleaned = label.replace(/[^A-Za-z0-9 ._-]/g, "-").trim().slice(0, MAX_COMMON_NAME);
|
|
78
|
+
return cleaned.length > 0 ? cleaned : FALLBACK_COMMON_NAME;
|
|
79
|
+
}
|
|
80
|
+
// ─── A DER writer, as much of one as a CSR needs ───
|
|
81
|
+
//
|
|
82
|
+
// Tags are written as constants rather than an enum: there are five of them,
|
|
83
|
+
// each is used once, and a reader checking this file against RFC 2986 wants the
|
|
84
|
+
// byte.
|
|
85
|
+
/** `INTEGER 0` — `version` is v1, and v1 is zero. */
|
|
86
|
+
const DER_VERSION_V1 = Uint8Array.from([0x02, 0x01, 0x00]);
|
|
87
|
+
/** `[0]` constructed, zero-length: the empty attribute set. */
|
|
88
|
+
const DER_EMPTY_ATTRIBUTES = Uint8Array.from([0xa0, 0x00]);
|
|
89
|
+
/**
|
|
90
|
+
* `AlgorithmIdentifier` for `sha256WithRSAEncryption` — OID
|
|
91
|
+
* 1.2.840.113549.1.1.11, with the explicit `NULL` parameters RFC 4055 §5
|
|
92
|
+
* requires for it. Written out because it is a constant in every request this
|
|
93
|
+
* file produces; an OID encoder would be more code and one more thing to get
|
|
94
|
+
* wrong.
|
|
95
|
+
*/
|
|
96
|
+
const DER_SHA256_WITH_RSA = Uint8Array.from([
|
|
97
|
+
0x30, 0x0d, 0x06, 0x09, 0x2a, 0x86, 0x48, 0x86, 0xf7, 0x0d, 0x01, 0x01, 0x0b, 0x05, 0x00,
|
|
98
|
+
]);
|
|
99
|
+
/** OID 2.5.4.3, `id-at-commonName`. */
|
|
100
|
+
const DER_OID_COMMON_NAME = Uint8Array.from([0x06, 0x03, 0x55, 0x04, 0x03]);
|
|
101
|
+
/** `SEQUENCE` of the given already-encoded members. */
|
|
102
|
+
function derSequence(...members) {
|
|
103
|
+
return derTagged(0x30, concat(members));
|
|
104
|
+
}
|
|
105
|
+
/** `RDNSequence` with the one relative distinguished name `CN=<value>`. */
|
|
106
|
+
function derName(commonName) {
|
|
107
|
+
const attribute = derSequence(DER_OID_COMMON_NAME, derUtf8String(commonName));
|
|
108
|
+
const rdn = derTagged(0x31, attribute); // SET
|
|
109
|
+
return derSequence(rdn);
|
|
110
|
+
}
|
|
111
|
+
function derUtf8String(value) {
|
|
112
|
+
return derTagged(0x0c, new TextEncoder().encode(value));
|
|
113
|
+
}
|
|
114
|
+
/**
|
|
115
|
+
* `BIT STRING` holding whole bytes.
|
|
116
|
+
*
|
|
117
|
+
* The leading zero is the count of unused bits in the final byte. A signature
|
|
118
|
+
* is a whole number of bytes, so it is always zero — and it is always present,
|
|
119
|
+
* because a `BIT STRING` without it is a malformed one.
|
|
120
|
+
*/
|
|
121
|
+
function derBitString(bytes) {
|
|
122
|
+
return derTagged(0x03, concat([Uint8Array.from([0x00]), bytes]));
|
|
123
|
+
}
|
|
124
|
+
/** `tag | length | content`, with the length in DER's short or long form. */
|
|
125
|
+
function derTagged(tag, content) {
|
|
126
|
+
return concat([Uint8Array.from([tag]), derLength(content.length), content]);
|
|
127
|
+
}
|
|
128
|
+
/**
|
|
129
|
+
* DER's definite length.
|
|
130
|
+
*
|
|
131
|
+
* Under 128 the length is the byte. At or above it, the first byte is `0x80`
|
|
132
|
+
* plus the number of bytes that follow, big-endian and with no leading zero —
|
|
133
|
+
* the "minimal number of octets" DER insists on, which is why this counts the
|
|
134
|
+
* bytes rather than always writing four.
|
|
135
|
+
*/
|
|
136
|
+
function derLength(length) {
|
|
137
|
+
if (length < 0x80)
|
|
138
|
+
return Uint8Array.from([length]);
|
|
139
|
+
const bytes = [];
|
|
140
|
+
for (let rest = length; rest > 0; rest = Math.floor(rest / 256))
|
|
141
|
+
bytes.unshift(rest % 256);
|
|
142
|
+
return Uint8Array.from([0x80 | bytes.length, ...bytes]);
|
|
143
|
+
}
|
|
144
|
+
function concat(parts) {
|
|
145
|
+
const total = parts.reduce((sum, part) => sum + part.length, 0);
|
|
146
|
+
const out = new Uint8Array(total);
|
|
147
|
+
let offset = 0;
|
|
148
|
+
for (const part of parts) {
|
|
149
|
+
out.set(part, offset);
|
|
150
|
+
offset += part.length;
|
|
151
|
+
}
|
|
152
|
+
return out;
|
|
153
|
+
}
|
|
154
|
+
/** DER to PEM: base64 in 64-column lines between the two armour lines. */
|
|
155
|
+
function derToPem(label, der) {
|
|
156
|
+
const body = Buffer.from(der).toString("base64").replace(/(.{64})/g, "$1\n").trimEnd();
|
|
157
|
+
return `-----BEGIN ${label}-----\n${body}\n-----END ${label}-----\n`;
|
|
158
|
+
}
|
|
159
|
+
//# sourceMappingURL=core-pairing-csr.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"core-pairing-csr.js","sourceRoot":"","sources":["../src/core-pairing-csr.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
/** The one route a client with no certificate may reach on a Core. */
|
|
2
|
+
export declare const CORE_PAIRING_REDEEM_PATH = "/v1/pair/redeem";
|
|
3
|
+
/**
|
|
4
|
+
* What the client says about itself.
|
|
5
|
+
*
|
|
6
|
+
* The Core keeps the label and ignores the rest. "Ignores" is the honest word
|
|
7
|
+
* and is checked by `parseRedeemRequest`: an optional field a server drops is a
|
|
8
|
+
* client courtesy, not a promise the server has broken.
|
|
9
|
+
*/
|
|
10
|
+
export type CorePairingClientInfo = {
|
|
11
|
+
/** The machine's own name for itself, e.g. a hostname. */
|
|
12
|
+
label?: string;
|
|
13
|
+
/**
|
|
14
|
+
* `process.platform`, sent by the CLI and the Panel.
|
|
15
|
+
*
|
|
16
|
+
* The Core reads it off the wire and stores nothing: it is not on
|
|
17
|
+
* {@link https://github.com/actana/control/issues/280 #280}'s paired-client
|
|
18
|
+
* record and `actana pair ls` does not show it. Surfacing it means adding a
|
|
19
|
+
* persisted field, which is a ticket rather than a parser change — #306's
|
|
20
|
+
* review raised it and it is tracked there.
|
|
21
|
+
*/
|
|
22
|
+
platform?: string;
|
|
23
|
+
};
|
|
24
|
+
/**
|
|
25
|
+
* The redemption request body.
|
|
26
|
+
*
|
|
27
|
+
* `sessionId` names the pairing session the code belongs to and is not
|
|
28
|
+
* optional: the Core hashes a candidate code together with the session id and
|
|
29
|
+
* refuses to search for a session a code might fit, which is what stops a code
|
|
30
|
+
* lifted from one session being replayed against another.
|
|
31
|
+
*/
|
|
32
|
+
export type CorePairingRedeemRequest = {
|
|
33
|
+
sessionId: string;
|
|
34
|
+
code: string;
|
|
35
|
+
client: CorePairingClientInfo;
|
|
36
|
+
/** PEM `CERTIFICATE REQUEST`. The private half is not in this object. */
|
|
37
|
+
csr: string;
|
|
38
|
+
};
|
|
39
|
+
/**
|
|
40
|
+
* The 200 body.
|
|
41
|
+
*
|
|
42
|
+
* Four fields, and the absence of a fifth is the point: there is no key here,
|
|
43
|
+
* because the Core never had one. `pairWithCore` supplies the fifth from the
|
|
44
|
+
* key it generated locally.
|
|
45
|
+
*/
|
|
46
|
+
export type CorePairingRedeemResponse = {
|
|
47
|
+
/** The `wss://host:port` core link to dial from now on. */
|
|
48
|
+
endpoint: string;
|
|
49
|
+
/** PEM CA certificate — the trust anchor for every later dial. */
|
|
50
|
+
caCert: string;
|
|
51
|
+
/** PEM client certificate, signed from the CSR just posted. */
|
|
52
|
+
clientCert: string;
|
|
53
|
+
/** The signed bearer for the `auth` frame. */
|
|
54
|
+
bearer: string;
|
|
55
|
+
};
|
|
56
|
+
/** A refusal body, as every non-200 answer from the pairing route is shaped. */
|
|
57
|
+
export type CorePairingRefusalBody = {
|
|
58
|
+
/** The Core's machine-readable reason, e.g. `pairing-refused`. */
|
|
59
|
+
code?: string;
|
|
60
|
+
/** The human-readable one. */
|
|
61
|
+
error?: string;
|
|
62
|
+
};
|
|
63
|
+
//# sourceMappingURL=core-pairing-wire.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"core-pairing-wire.d.ts","sourceRoot":"","sources":["../src/core-pairing-wire.ts"],"names":[],"mappings":"AAoBA,sEAAsE;AACtE,eAAO,MAAM,wBAAwB,oBAAoB,CAAC;AAE1D;;;;;;GAMG;AACH,MAAM,MAAM,qBAAqB,GAAG;IAClC,0DAA0D;IAC1D,KAAK,CAAC,EAAE,MAAM,CAAC;IACf;;;;;;;;OAQG;IACH,QAAQ,CAAC,EAAE,MAAM,CAAC;CACnB,CAAC;AAEF;;;;;;;GAOG;AACH,MAAM,MAAM,wBAAwB,GAAG;IACrC,SAAS,EAAE,MAAM,CAAC;IAClB,IAAI,EAAE,MAAM,CAAC;IACb,MAAM,EAAE,qBAAqB,CAAC;IAC9B,yEAAyE;IACzE,GAAG,EAAE,MAAM,CAAC;CACb,CAAC;AAEF;;;;;;GAMG;AACH,MAAM,MAAM,yBAAyB,GAAG;IACtC,2DAA2D;IAC3D,QAAQ,EAAE,MAAM,CAAC;IACjB,kEAAkE;IAClE,MAAM,EAAE,MAAM,CAAC;IACf,+DAA+D;IAC/D,UAAU,EAAE,MAAM,CAAC;IACnB,8CAA8C;IAC9C,MAAM,EAAE,MAAM,CAAC;CAChB,CAAC;AAEF,gFAAgF;AAChF,MAAM,MAAM,sBAAsB,GAAG;IACnC,kEAAkE;IAClE,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,8BAA8B;IAC9B,KAAK,CAAC,EAAE,MAAM,CAAC;CAChB,CAAC"}
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
// The pairing route's wire contract, and nothing else.
|
|
2
|
+
//
|
|
3
|
+
// This file exists so that the redeem request and response have **one**
|
|
4
|
+
// definition rather than two structurally identical ones — [ADR 0025][adr] D3:
|
|
5
|
+
// *"There is exactly one definition of every frame type, and a mirror is never
|
|
6
|
+
// the answer."* Until #306's review it was a mirror: `core-pairing.ts` declared
|
|
7
|
+
// the shapes for the client and `packages/core/src/core-pairing-routes.ts`
|
|
8
|
+
// hand-built the same shapes for the server, and nothing compiled the two
|
|
9
|
+
// against each other. A mirror does not fail; it disagrees, at runtime, on a
|
|
10
|
+
// wire, between two processes that each believe they are correct.
|
|
11
|
+
//
|
|
12
|
+
// It is a separate module from `core-pairing.ts` because of what D2 requires of
|
|
13
|
+
// anything the Core imports: **no I/O, no transport and no imports of its
|
|
14
|
+
// own.** `core-pairing.ts` dials, hashes and reads certificates, so a Core
|
|
15
|
+
// importing it would be a Core dialling itself. This file has no imports, and
|
|
16
|
+
// that is a property to preserve rather than a coincidence — it is the reason
|
|
17
|
+
// D2's list is allowed to grow to include it.
|
|
18
|
+
//
|
|
19
|
+
// [adr]: ../../docs/adr/0025-the-protocol-ships-with-the-client.md
|
|
20
|
+
/** The one route a client with no certificate may reach on a Core. */
|
|
21
|
+
export const CORE_PAIRING_REDEEM_PATH = "/v1/pair/redeem";
|
|
22
|
+
//# sourceMappingURL=core-pairing-wire.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"core-pairing-wire.js","sourceRoot":"","sources":["../src/core-pairing-wire.ts"],"names":[],"mappings":"AAAA,uDAAuD;AACvD,EAAE;AACF,wEAAwE;AACxE,+EAA+E;AAC/E,+EAA+E;AAC/E,gFAAgF;AAChF,2EAA2E;AAC3E,0EAA0E;AAC1E,6EAA6E;AAC7E,kEAAkE;AAClE,EAAE;AACF,gFAAgF;AAChF,0EAA0E;AAC1E,2EAA2E;AAC3E,8EAA8E;AAC9E,8EAA8E;AAC9E,8CAA8C;AAC9C,EAAE;AACF,mEAAmE;AAEnE,sEAAsE;AACtE,MAAM,CAAC,MAAM,wBAAwB,GAAG,iBAAiB,CAAC"}
|
|
@@ -0,0 +1,211 @@
|
|
|
1
|
+
import type { CoreRegistrationBlob } from "./core-registration-blob.ts";
|
|
2
|
+
export { CORE_PAIRING_REDEEM_PATH, type CorePairingClientInfo, type CorePairingRedeemRequest, type CorePairingRedeemResponse, type CorePairingRefusalBody, } from "./core-pairing-wire.ts";
|
|
3
|
+
/**
|
|
4
|
+
* Why a pairing attempt did not produce a blob.
|
|
5
|
+
*
|
|
6
|
+
* The list is what a caller can *act* on, and it stops where the Core's own
|
|
7
|
+
* answers stop. `refused` covers a wrong code, an expired session, one already
|
|
8
|
+
* redeemed and one whose attempts are spent, because the Core answers all four
|
|
9
|
+
* with one status and one body on purpose (`core-pairing-routes.ts`, "every
|
|
10
|
+
* refusal is the same refusal"): telling them apart on the wire would tell an
|
|
11
|
+
* attacker whether a session exists and whether their last guess was closer.
|
|
12
|
+
* A client that reported four different things would be inventing three of
|
|
13
|
+
* them. The distinction the operator needs is in the Core's audit log, which
|
|
14
|
+
* is where #282 put it deliberately.
|
|
15
|
+
*/
|
|
16
|
+
export type CorePairingFailure =
|
|
17
|
+
/** The address could not be read as a Core's HTTPS or core-link address. */
|
|
18
|
+
"bad-address"
|
|
19
|
+
/** The code was not eight characters, or named no pairing session. */
|
|
20
|
+
| "bad-code"
|
|
21
|
+
/** The expected fingerprint was not a SHA-256 fingerprint. */
|
|
22
|
+
| "bad-fingerprint"
|
|
23
|
+
/** Nothing answered at that address, or the dial timed out. */
|
|
24
|
+
| "unreachable"
|
|
25
|
+
/** The Core presented a chain with no CA in it — nothing to pin. */
|
|
26
|
+
| "no-ca-presented"
|
|
27
|
+
/** No fingerprint was given to check against. The code was not sent. */
|
|
28
|
+
| "fingerprint-unconfirmed"
|
|
29
|
+
/** The presented CA is not the expected one. The code was not sent. */
|
|
30
|
+
| "fingerprint-mismatch"
|
|
31
|
+
/**
|
|
32
|
+
* The expected CA, on an address its certificate does not cover.
|
|
33
|
+
*
|
|
34
|
+
* Its own failure rather than a mismatch, because it is not an attack and
|
|
35
|
+
* saying so sends the operator hunting for one: a Core's server certificate
|
|
36
|
+
* covers the host it was set up for plus loopback, so reaching a Core over a
|
|
37
|
+
* second interface, a tunnel or a DNS name added later fails here with the
|
|
38
|
+
* fingerprint matching perfectly.
|
|
39
|
+
*/
|
|
40
|
+
| "hostname-mismatch"
|
|
41
|
+
/** The expected CA, and a certificate that is expired or otherwise unusable. */
|
|
42
|
+
| "certificate-invalid"
|
|
43
|
+
/** Wrong, expired, already redeemed, or out of attempts — see above. */
|
|
44
|
+
| "refused"
|
|
45
|
+
/** Too many attempts from this client, too fast. Try again later. */
|
|
46
|
+
| "rate-limited"
|
|
47
|
+
/** The Core would not accept the request itself — a bug on this side. */
|
|
48
|
+
| "rejected"
|
|
49
|
+
/** There is no pairing endpoint on that Core. */
|
|
50
|
+
| "not-pairable"
|
|
51
|
+
/** The Core failed while handling the redemption. */
|
|
52
|
+
| "core-error"
|
|
53
|
+
/** A 200 that was not a redemption response. */
|
|
54
|
+
| "malformed-response";
|
|
55
|
+
/** Everything a failure knows beyond its {@link CorePairingFailure}. */
|
|
56
|
+
export type CorePairingErrorDetail = {
|
|
57
|
+
/** The HTTP status, when the failure came from one. */
|
|
58
|
+
status?: number;
|
|
59
|
+
/** `retry-after`, in seconds, on a `rate-limited` failure. */
|
|
60
|
+
retryAfterSeconds?: number;
|
|
61
|
+
/** The Core's own refusal code, when it sent one. */
|
|
62
|
+
coreCode?: string;
|
|
63
|
+
/** The fingerprint the caller was told to expect. */
|
|
64
|
+
expectedFingerprint?: string;
|
|
65
|
+
/** The fingerprint the Core actually presented. */
|
|
66
|
+
presentedFingerprint?: string;
|
|
67
|
+
/** The CA the Core presented, PEM — for a UI that wants to show it. */
|
|
68
|
+
presentedCaCert?: string;
|
|
69
|
+
/** The TLS or OpenSSL code behind a dial failure, e.g. `CERT_HAS_EXPIRED`. */
|
|
70
|
+
tlsCode?: string;
|
|
71
|
+
};
|
|
72
|
+
/**
|
|
73
|
+
* A pairing attempt that did not produce a blob.
|
|
74
|
+
*
|
|
75
|
+
* One class with a {@link CorePairingFailure} rather than a class per failure:
|
|
76
|
+
* the CLI (#285) and the Panel (#286) both switch on the reason to write a
|
|
77
|
+
* sentence, and a `switch` over a union is checked by the compiler where a
|
|
78
|
+
* chain of `instanceof` is not.
|
|
79
|
+
*/
|
|
80
|
+
export declare class CorePairingError extends Error {
|
|
81
|
+
readonly name = "CorePairingError";
|
|
82
|
+
/** Which failure this is. Switch on it; do not read the message. */
|
|
83
|
+
readonly failure: CorePairingFailure;
|
|
84
|
+
/** Whatever the failure knows beyond its reason. */
|
|
85
|
+
readonly detail: CorePairingErrorDetail;
|
|
86
|
+
constructor(failure: CorePairingFailure, message: string, detail?: CorePairingErrorDetail, options?: {
|
|
87
|
+
cause?: unknown;
|
|
88
|
+
});
|
|
89
|
+
}
|
|
90
|
+
/** What a bootstrap dial learns about a Core before anything is trusted. */
|
|
91
|
+
export type CorePairingIdentity = {
|
|
92
|
+
/** SHA-256 over the CA's DER, colon-separated uppercase hex. */
|
|
93
|
+
fingerprint: string;
|
|
94
|
+
/** The PEM CA certificate that fingerprint is of. */
|
|
95
|
+
caCert: string;
|
|
96
|
+
/** The host that was dialled. */
|
|
97
|
+
host: string;
|
|
98
|
+
/** The port that was dialled. */
|
|
99
|
+
port: number;
|
|
100
|
+
/** `https://host:port` — where a redemption would be posted. */
|
|
101
|
+
httpsOrigin: string;
|
|
102
|
+
};
|
|
103
|
+
/** How long a dial or a redemption may take before it is called unreachable. */
|
|
104
|
+
export declare const DEFAULT_PAIRING_TIMEOUT_MS = 15000;
|
|
105
|
+
/**
|
|
106
|
+
* Dial a Core and report the CA it presents — **without a code to send**.
|
|
107
|
+
*
|
|
108
|
+
* This is the first-contact mode, and the reason it is a separate function
|
|
109
|
+
* rather than a flag is that it takes no code: a UI that shows the operator's
|
|
110
|
+
* fingerprint beside the Core's, and asks a human whether they match, cannot
|
|
111
|
+
* leak a secret it was never given. {@link pairWithCore} calls it too, so the
|
|
112
|
+
* fingerprint a caller confirms is computed by the same code that later
|
|
113
|
+
* enforces it.
|
|
114
|
+
*
|
|
115
|
+
* The dial is unverified, because at this point in the flow there is nothing to
|
|
116
|
+
* verify against — that is what the fingerprint the operator read out is for.
|
|
117
|
+
* Nothing is sent on this connection and it is closed as soon as the chain has
|
|
118
|
+
* been read.
|
|
119
|
+
*/
|
|
120
|
+
export declare function fetchCorePairingIdentity(opts: {
|
|
121
|
+
/** `host:port`, `https://host:port` or `wss://host:port`. */
|
|
122
|
+
address: string;
|
|
123
|
+
/** Defaults to {@link DEFAULT_PAIRING_TIMEOUT_MS}. */
|
|
124
|
+
timeoutMs?: number;
|
|
125
|
+
}): Promise<CorePairingIdentity>;
|
|
126
|
+
export type PairWithCoreOptions = {
|
|
127
|
+
/** The Core's address: `host:port`, `https://host:port` or `wss://host:port`. */
|
|
128
|
+
address: string;
|
|
129
|
+
/**
|
|
130
|
+
* The pairing code the operator read out — `XXXX-XXXX`, in any case and with
|
|
131
|
+
* or without the hyphen.
|
|
132
|
+
*
|
|
133
|
+
* A code names a session, and the Core will not go looking for which one, so
|
|
134
|
+
* the session id has to travel with it. Either pass it as `sessionId`, or
|
|
135
|
+
* pass a single `<sessionId>:<XXXX-XXXX>` string here and this reads both out
|
|
136
|
+
* of it. Which of the two an operator is given is #280's to settle across
|
|
137
|
+
* `actana pair new` (#283) and `actana core pair` (#285); both forms parse
|
|
138
|
+
* here so that neither is a change to this module.
|
|
139
|
+
*/
|
|
140
|
+
code: string;
|
|
141
|
+
/** The pairing session the code belongs to, when `code` does not carry it. */
|
|
142
|
+
sessionId?: string;
|
|
143
|
+
/**
|
|
144
|
+
* The CA fingerprint the operator read out, in any of the forms a human
|
|
145
|
+
* copies it in: colon-separated or not, upper or lower case.
|
|
146
|
+
*
|
|
147
|
+
* **Absent is not "skip the check".** With no fingerprint this function
|
|
148
|
+
* refuses with `fingerprint-unconfirmed` and reports the presented one in the
|
|
149
|
+
* error, having sent no code — see {@link fetchCorePairingIdentity}.
|
|
150
|
+
*/
|
|
151
|
+
expectedCaFingerprint?: string | null;
|
|
152
|
+
/** What this machine calls itself, for the operator's `actana pair ls`. */
|
|
153
|
+
label?: string;
|
|
154
|
+
/** This machine's platform, e.g. `process.platform`. */
|
|
155
|
+
platform?: string;
|
|
156
|
+
/** Defaults to {@link DEFAULT_PAIRING_TIMEOUT_MS}, per connection. */
|
|
157
|
+
timeoutMs?: number;
|
|
158
|
+
};
|
|
159
|
+
/**
|
|
160
|
+
* Pair with a Core and return the credential it issued.
|
|
161
|
+
*
|
|
162
|
+
* The result is a {@link CoreRegistrationBlob} and nothing downstream can tell
|
|
163
|
+
* it from a hand-carried one: `coreConnectionFromBlob` unpacks it,
|
|
164
|
+
* `httpsBaseUrlFor` derives the HTTPS origin, the PEMs go into the mTLS
|
|
165
|
+
* handshake and the bearer into the `auth` frame — exactly as they do today.
|
|
166
|
+
* `clientKey` is the key generated on this machine a few lines above; it was
|
|
167
|
+
* never sent, and the Core has never seen it.
|
|
168
|
+
*
|
|
169
|
+
* Throws {@link CorePairingError} for everything that is not a blob.
|
|
170
|
+
*/
|
|
171
|
+
export declare function pairWithCore(opts: PairWithCoreOptions): Promise<CoreRegistrationBlob>;
|
|
172
|
+
/** A pairing code, and the session it names. */
|
|
173
|
+
export type PairingTicket = {
|
|
174
|
+
sessionId: string;
|
|
175
|
+
code: string;
|
|
176
|
+
};
|
|
177
|
+
/**
|
|
178
|
+
* Read a ticket out of what a human typed.
|
|
179
|
+
*
|
|
180
|
+
* The code is checked for *shape* — eight alphanumerics, hyphens and spaces
|
|
181
|
+
* ignored — and not against the Core's alphabet. That is a deliberate stop:
|
|
182
|
+
* the alphabet is an internal of `packages/shared` (which this package may not
|
|
183
|
+
* import) and mirroring it here would be a copy free to drift, which ADR 0025
|
|
184
|
+
* D3 is about. What the shape check buys is worth having on its own: a code
|
|
185
|
+
* that could not be right whatever the alphabet is never spends one of the
|
|
186
|
+
* five attempts the operator's session has.
|
|
187
|
+
*/
|
|
188
|
+
export declare function parsePairingTicket(input: string, sessionId?: string): PairingTicket;
|
|
189
|
+
/** A Core's address, in the two forms this module needs it. */
|
|
190
|
+
type CoreAddress = {
|
|
191
|
+
host: string;
|
|
192
|
+
port: number;
|
|
193
|
+
httpsOrigin: string;
|
|
194
|
+
};
|
|
195
|
+
/**
|
|
196
|
+
* Read `host:port`, `https://host:port` or `wss://host:port`.
|
|
197
|
+
*
|
|
198
|
+
* `ws://` and `http://` are refused rather than upgraded: there is no
|
|
199
|
+
* certificate on a plaintext dial, so there is no fingerprint to check, and
|
|
200
|
+
* pairing over one would be the silent unverified exchange this module exists
|
|
201
|
+
* to make impossible. A caller that meant the secure port should say so.
|
|
202
|
+
*/
|
|
203
|
+
export declare function parseCoreAddress(address: string): CoreAddress;
|
|
204
|
+
/**
|
|
205
|
+
* A fingerprint as this module compares them: colon-separated uppercase hex,
|
|
206
|
+
* which is the form `actana pair new` prints and a human copies.
|
|
207
|
+
*/
|
|
208
|
+
export declare function parseFingerprint(input: string): string;
|
|
209
|
+
/** SHA-256 over a certificate's DER, in the form {@link parseFingerprint} yields. */
|
|
210
|
+
export declare function fingerprintOf(der: Uint8Array): string;
|
|
211
|
+
//# sourceMappingURL=core-pairing.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"core-pairing.d.ts","sourceRoot":"","sources":["../src/core-pairing.ts"],"names":[],"mappings":"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"}
|