@bitspark/archon-sdk 0.6.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.
@@ -0,0 +1,27 @@
1
+ /** The first four bytes of every envelope. */
2
+ export declare const ENVELOPE_MAGIC: Uint8Array<ArrayBuffer>;
3
+ /** The envelope format version. */
4
+ export declare const ENVELOPE_VERSION = 1;
5
+ /** The first byte of every signed envelope message. Distinct from the possession `SCHEME_TAG`. */
6
+ export declare const ENVELOPE_SCHEME_TAG = 2;
7
+ /** What `open` returns: the sealing key and the payload, both verified. */
8
+ export interface Opened {
9
+ /** The public key that sealed the envelope. Trusting it is the caller's decision. */
10
+ pubkey: Uint8Array;
11
+ /** The payload, verbatim. */
12
+ payload: Uint8Array;
13
+ }
14
+ /**
15
+ * Seal `payload` in `domain` with the key behind `seed`. Throws on an invalid domain (see
16
+ * `signInDomain`). An empty payload is allowed — the signed message is never empty
17
+ * because of the scheme tag.
18
+ */
19
+ export declare function seal(seed: Uint8Array, domain: string, payload: Uint8Array): Uint8Array;
20
+ /**
21
+ * Open `envelope`, which the caller expects to be sealed in `domain`. Throws — never
22
+ * returns a payload — when the bytes are not an envelope (magic, version, length), the
23
+ * envelope claims a different domain, or the signature does not verify.
24
+ */
25
+ export declare function open(envelope: Uint8Array, domain: string): Opened;
26
+ /** The pinned layout of what gets signed: the scheme tag, then the payload verbatim. */
27
+ export declare function envelopeMessageBytes(payload: Uint8Array): Uint8Array;
@@ -0,0 +1,91 @@
1
+ // The signed envelope — THESE BYTES, SIGNED BY THIS KEY, IN THIS DOMAIN.
2
+ //
3
+ // A fixed binary container, JWS-shaped and deliberately not JWT-shaped:
4
+ //
5
+ // "arcn" ‖ 0x01 ‖ u8(len domain) ‖ domain ‖ pubkey[32] ‖ signature[64] ‖ payload
6
+ //
7
+ // where `signature = signInDomain(seed, domain, SCHEME_TAG ‖ payload)`. The domain is
8
+ // bound cryptographically (it is the RFC 8032 context), the public key is bound by
9
+ // verification, and the payload is opaque: the envelope says nothing about what it
10
+ // means. WHAT IS NOT HERE, ON PURPOSE: expiry, issuer, audience, key-id, nonce. Each is
11
+ // either policy — whose clock, whose trust? — or a second spelling of the key, and both
12
+ // belong to the consumer.
13
+ //
14
+ // `open` takes the domain the VERIFIER expects and refuses an envelope that claims
15
+ // another. The verifier chooses the domain; an envelope never gets to choose it for
16
+ // them. Whether to trust the key it names is, again, the verifier's.
17
+ import { getPublicKey, signInDomain, verifyInDomain, MAX_DOMAIN_SIZE, PUBLIC_KEY_SIZE, SIGNATURE_SIZE, } from "@bitspark/archon";
18
+ /** The first four bytes of every envelope. */
19
+ export const ENVELOPE_MAGIC = new Uint8Array([0x61, 0x72, 0x63, 0x6e]); // "arcn"
20
+ /** The envelope format version. */
21
+ export const ENVELOPE_VERSION = 0x01;
22
+ /** The first byte of every signed envelope message. Distinct from the possession `SCHEME_TAG`. */
23
+ export const ENVELOPE_SCHEME_TAG = 0x02;
24
+ /**
25
+ * Seal `payload` in `domain` with the key behind `seed`. Throws on an invalid domain (see
26
+ * `signInDomain`). An empty payload is allowed — the signed message is never empty
27
+ * because of the scheme tag.
28
+ */
29
+ export function seal(seed, domain, payload) {
30
+ const signature = signInDomain(seed, domain, envelopeMessageBytes(payload));
31
+ const pubkey = getPublicKey(seed);
32
+ const d = new TextEncoder().encode(domain);
33
+ const out = new Uint8Array(4 + 1 + 1 + d.length + PUBLIC_KEY_SIZE + SIGNATURE_SIZE + payload.length);
34
+ let at = 0;
35
+ out.set(ENVELOPE_MAGIC, at);
36
+ at += 4;
37
+ out[at++] = ENVELOPE_VERSION;
38
+ out[at++] = d.length; // ≤ 255: signInDomain has already checked it
39
+ out.set(d, at);
40
+ at += d.length;
41
+ out.set(pubkey, at);
42
+ at += PUBLIC_KEY_SIZE;
43
+ out.set(signature, at);
44
+ at += SIGNATURE_SIZE;
45
+ out.set(payload, at);
46
+ return out;
47
+ }
48
+ /**
49
+ * Open `envelope`, which the caller expects to be sealed in `domain`. Throws — never
50
+ * returns a payload — when the bytes are not an envelope (magic, version, length), the
51
+ * envelope claims a different domain, or the signature does not verify.
52
+ */
53
+ export function open(envelope, domain) {
54
+ let at = 0;
55
+ const take = (n) => {
56
+ if (at + n > envelope.length)
57
+ throw new Error(`envelope truncated at byte ${envelope.length}`);
58
+ const s = envelope.subarray(at, at + n);
59
+ at += n;
60
+ return s;
61
+ };
62
+ const magic = take(4);
63
+ if (!ENVELOPE_MAGIC.every((b, i) => magic[i] === b))
64
+ throw new Error("not an envelope: bad magic");
65
+ const version = take(1)[0];
66
+ if (version !== ENVELOPE_VERSION)
67
+ throw new Error(`unsupported envelope version ${version}`);
68
+ const dlen = take(1)[0];
69
+ if (dlen === 0 || dlen > MAX_DOMAIN_SIZE)
70
+ throw new Error(`envelope domain length ${dlen} out of range`);
71
+ const claimed = take(dlen);
72
+ const expected = new TextEncoder().encode(domain);
73
+ if (claimed.length !== expected.length || !expected.every((b, i) => claimed[i] === b)) {
74
+ throw new Error("envelope claims a different domain");
75
+ }
76
+ const pubkey = new Uint8Array(take(PUBLIC_KEY_SIZE));
77
+ const signature = take(SIGNATURE_SIZE);
78
+ const payload = new Uint8Array(envelope.subarray(at));
79
+ if (!verifyInDomain(pubkey, domain, envelopeMessageBytes(payload), signature)) {
80
+ throw new Error("envelope signature does not verify");
81
+ }
82
+ return { pubkey, payload };
83
+ }
84
+ /** The pinned layout of what gets signed: the scheme tag, then the payload verbatim. */
85
+ export function envelopeMessageBytes(payload) {
86
+ const out = new Uint8Array(1 + payload.length);
87
+ out[0] = ENVELOPE_SCHEME_TAG;
88
+ out.set(payload, 1);
89
+ return out;
90
+ }
91
+ //# sourceMappingURL=envelope.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"envelope.js","sourceRoot":"","sources":["../../src/envelope.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,4 @@
1
+ export * from "./possession.js";
2
+ export * from "./envelope.js";
3
+ export * from "./login.js";
4
+ export * from "./login-audience.js";
@@ -0,0 +1,19 @@
1
+ // archon-sdk — one layer above the identity floor.
2
+ //
3
+ // @bitspark/archon answers "is this really them?" over bytes. This package answers the
4
+ // two questions every protocol asks next, without knowing the protocol:
5
+ //
6
+ // - possession — can they sign, right now, for this channel? Challenge/response; the
7
+ // binding is what makes it a proof, so an empty one is refused.
8
+ // - envelope — these bytes, signed by this key, in this domain. JWS, never JWT.
9
+ //
10
+ // The rule that keeps this layer honest: entropy, time and channel binding are
11
+ // ARGUMENTS. This package never sources them. That is what lets every byte it emits be a
12
+ // deterministic function of its inputs and be pinned by vectors/sdk.json across three
13
+ // languages — the same way the floor is pinned. Connection setup is not here and never
14
+ // will be: that is the transport's (ADR 0004).
15
+ export * from "./possession.js";
16
+ export * from "./envelope.js";
17
+ export * from "./login.js";
18
+ export * from "./login-audience.js";
19
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/index.ts"],"names":[],"mappings":"AAAA,mDAAmD;AACnD,EAAE;AACF,uFAAuF;AACvF,wEAAwE;AACxE,EAAE;AACF,qFAAqF;AACrF,kEAAkE;AAClE,gFAAgF;AAChF,EAAE;AACF,+EAA+E;AAC/E,yFAAyF;AACzF,sFAAsF;AACtF,uFAAuF;AACvF,+CAA+C;AAC/C,cAAc,iBAAiB,CAAC;AAChC,cAAc,eAAe,CAAC;AAC9B,cAAc,YAAY,CAAC;AAC3B,cAAc,qBAAqB,CAAC"}
@@ -0,0 +1,14 @@
1
+ /**
2
+ * The audience the login proof binds and the request id's bytes, from the URL the person was
3
+ * told to run. Scheme is http/https/ws/wss (case-insensitive; ws → http, wss → https); the
4
+ * host is an ASCII reg-name or a bracketed IPv6 literal, lowercased; a port is 1..=65535
5
+ * without a leading zero and is omitted from the audience when it is the folded scheme's
6
+ * default (80, 443); segments are kept as written (case preserved, percent-escapes never
7
+ * decoded); the id is lowercase hex of even length and is returned decoded. Throws on any
8
+ * non-ASCII, whitespace or control character, userinfo, query, fragment, empty segment,
9
+ * `.`/`..` segment, malformed escape, or a penultimate segment other than `login`.
10
+ */
11
+ export declare function deriveAudience(url: string): {
12
+ audience: string;
13
+ id: Uint8Array;
14
+ };
@@ -0,0 +1,138 @@
1
+ // deriveAudience — the invocation URL → { audience, id bytes }, docs/login.md §2.1.
2
+ //
3
+ // Part of the scheme, not of any CLI: three URL parsers normalise three ways (a WHATWG URL
4
+ // drops a default port and userinfo, net/url decodes the path, a hand-rolled split keeps
5
+ // userinfo as the host) and the audience is the first field of the binding, so one URL must
6
+ // yield one audience in every lane. This function accepts exactly the grammar of §2.1 and
7
+ // REFUSES everything else rather than normalising it — deliberately not the WHATWG `URL`
8
+ // class, which is a normaliser.
9
+ //
10
+ // invocation = scheme "://" host [ ":" port ] *( "/" segment ) "/login/" id
11
+ /**
12
+ * The audience the login proof binds and the request id's bytes, from the URL the person was
13
+ * told to run. Scheme is http/https/ws/wss (case-insensitive; ws → http, wss → https); the
14
+ * host is an ASCII reg-name or a bracketed IPv6 literal, lowercased; a port is 1..=65535
15
+ * without a leading zero and is omitted from the audience when it is the folded scheme's
16
+ * default (80, 443); segments are kept as written (case preserved, percent-escapes never
17
+ * decoded); the id is lowercase hex of even length and is returned decoded. Throws on any
18
+ * non-ASCII, whitespace or control character, userinfo, query, fragment, empty segment,
19
+ * `.`/`..` segment, malformed escape, or a penultimate segment other than `login`.
20
+ */
21
+ export function deriveAudience(url) {
22
+ for (let i = 0; i < url.length; i++) {
23
+ const c = url.charCodeAt(i);
24
+ if (c <= 0x20 || c >= 0x7f) {
25
+ throw new Error(`login: URL character ${i} is U+${c.toString(16).toUpperCase().padStart(4, "0")} — only printable ASCII is accepted`);
26
+ }
27
+ }
28
+ const sep = url.indexOf("://");
29
+ if (sep < 0)
30
+ throw new Error("login: URL has no scheme");
31
+ const [scheme, defaultPort] = foldScheme(url.slice(0, sep));
32
+ const rest = url.slice(sep + 3);
33
+ const slash = rest.indexOf("/");
34
+ if (slash < 0)
35
+ throw new Error("login: URL has no path — expected <audience>/login/<id>");
36
+ const [host, port] = parseAuthority(rest.slice(0, slash));
37
+ const path = rest.slice(slash);
38
+ if (path.includes("?") || path.includes("#")) {
39
+ throw new Error("login: URL carries a query or fragment — expected <audience>/login/<id>");
40
+ }
41
+ const segments = path.slice(1).split("/");
42
+ segments.forEach((seg, i) => checkSegment(seg, i));
43
+ if (segments.length < 2)
44
+ throw new Error("login: URL is not <audience>/login/<id>");
45
+ const penultimate = segments[segments.length - 2];
46
+ if (penultimate !== "login") {
47
+ throw new Error(`login: expected <audience>/login/<id>, got ${JSON.stringify(penultimate)} as the penultimate segment`);
48
+ }
49
+ const id = decodeId(segments[segments.length - 1]);
50
+ let audience = `${scheme}://${host}`;
51
+ if (port !== "" && port !== defaultPort)
52
+ audience += `:${port}`;
53
+ for (const seg of segments.slice(0, -2))
54
+ audience += `/${seg}`;
55
+ return { audience, id };
56
+ }
57
+ function foldScheme(s) {
58
+ switch (s.toLowerCase()) {
59
+ case "http":
60
+ case "ws":
61
+ return ["http", "80"];
62
+ case "https":
63
+ case "wss":
64
+ return ["https", "443"];
65
+ default:
66
+ throw new Error(`login: unsupported URL scheme ${JSON.stringify(s)} — want http(s) or ws(s)`);
67
+ }
68
+ }
69
+ function parseAuthority(auth) {
70
+ if (auth.includes("@"))
71
+ throw new Error("login: URL carries userinfo — refused");
72
+ if (auth === "")
73
+ throw new Error("login: URL has no host");
74
+ if (auth.startsWith("[")) {
75
+ const end = auth.indexOf("]");
76
+ if (end < 0)
77
+ throw new Error("login: unterminated IPv6 literal");
78
+ const lit = auth.slice(1, end).toLowerCase();
79
+ if (lit.length < 2 || !lit.includes(":") || !/^[0-9a-f:.]+$/.test(lit)) {
80
+ throw new Error("login: malformed IPv6 literal");
81
+ }
82
+ const tail = auth.slice(end + 1);
83
+ if (tail === "")
84
+ return [`[${lit}]`, ""];
85
+ if (!tail.startsWith(":"))
86
+ throw new Error("login: bytes after the IPv6 literal — refused");
87
+ return [`[${lit}]`, checkPort(tail.slice(1))];
88
+ }
89
+ const colon = auth.indexOf(":");
90
+ const name = colon < 0 ? auth : auth.slice(0, colon);
91
+ const port = colon < 0 ? "" : checkPort(auth.slice(colon + 1));
92
+ const host = name.toLowerCase();
93
+ for (const label of host.split(".")) {
94
+ if (label === "")
95
+ throw new Error("login: host has an empty label — refused");
96
+ if (!/^[a-z0-9-]+$/.test(label)) {
97
+ throw new Error("login: host has a character outside [a-z0-9-.] — refused (IDNs must be given as punycode)");
98
+ }
99
+ }
100
+ return [host, port];
101
+ }
102
+ function checkPort(p) {
103
+ if (!/^[0-9]+$/.test(p) || (p.length > 1 && p.startsWith("0")))
104
+ throw new Error(`login: port ${JSON.stringify(p)} — refused`);
105
+ const n = Number(p);
106
+ if (n < 1 || n > 65535)
107
+ throw new Error(`login: port ${JSON.stringify(p)} is out of range`);
108
+ return p;
109
+ }
110
+ const SEGMENT_CHAR = /^[A-Za-z0-9\-._~!$&'()*+,;=:@]$/;
111
+ function checkSegment(seg, i) {
112
+ if (seg === "")
113
+ throw new Error(`login: path segment ${i}: empty segment (a trailing slash or \`//\`) — refused`);
114
+ if (seg === "." || seg === "..")
115
+ throw new Error(`login: path segment ${i}: dot segment — refused`);
116
+ for (let j = 0; j < seg.length; j++) {
117
+ const c = seg[j];
118
+ if (c === "%") {
119
+ if (j + 2 >= seg.length || !/^[0-9A-Fa-f]{2}$/.test(seg.slice(j + 1, j + 3))) {
120
+ throw new Error(`login: path segment ${i}: malformed percent-escape — refused`);
121
+ }
122
+ j += 2;
123
+ }
124
+ else if (!SEGMENT_CHAR.test(c)) {
125
+ throw new Error(`login: path segment ${i}: ${JSON.stringify(c)} is not allowed in a path segment — refused`);
126
+ }
127
+ }
128
+ }
129
+ function decodeId(s) {
130
+ if (s.length < 2 || s.length % 2 !== 0 || !/^[0-9a-f]+$/.test(s)) {
131
+ throw new Error(`login: id ${JSON.stringify(s)} is not lowercase hex of even length`);
132
+ }
133
+ const out = new Uint8Array(s.length / 2);
134
+ for (let i = 0; i < out.length; i++)
135
+ out[i] = parseInt(s.slice(2 * i, 2 * i + 2), 16);
136
+ return out;
137
+ }
138
+ //# sourceMappingURL=login-audience.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"login-audience.js","sourceRoot":"","sources":["../../src/login-audience.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,63 @@
1
+ /** The RFC 8032 context every login-scheme proof is made in. */
2
+ export declare const LOGIN_DOMAIN = "archon-login/1";
3
+ /** The binding's first byte for the person's proof (made by P). */
4
+ export declare const LOGIN_ROLE_LOGIN = 1;
5
+ /** The binding's first byte for the browser's collect proof (made by K). */
6
+ export declare const LOGIN_ROLE_COLLECT = 2;
7
+ /**
8
+ * The longest audience, id or scope entry, in bytes — the u16 length prefix's bound. It also
9
+ * bounds the scope entry count and the whole binding (which must fit the possession scheme's
10
+ * own u16 field).
11
+ */
12
+ export declare const LOGIN_MAX_FIELD_SIZE = 65535;
13
+ /**
14
+ * A pending login as the server issued it and the CLI reads it back (docs/login.md §3.1).
15
+ * The nonce is validated by the possession scheme (≥ 16 bytes) at proof time; the other
16
+ * fields by `loginBinding`.
17
+ */
18
+ export interface LoginRequest {
19
+ /** The server's, opaque, 1..=LOGIN_MAX_FIELD_SIZE bytes. */
20
+ id: Uint8Array;
21
+ /** The server's fresh entropy, one per request. */
22
+ nonce: Uint8Array;
23
+ /** K's public key, exactly PUBLIC_KEY_SIZE bytes. */
24
+ browser: Uint8Array;
25
+ /** Ordered; each 1..=LOGIN_MAX_FIELD_SIZE bytes of UTF-8 with no control characters. */
26
+ scope: string[];
27
+ /** The delegation's requested lifetime in seconds, > 0 (a u32). */
28
+ validFor: number;
29
+ }
30
+ /**
31
+ * The bytes both proofs are bound to, for `role` over `audience` and `req` (docs/login.md
32
+ * §3.2):
33
+ *
34
+ * role ‖ u16be(len audience) ‖ audience ‖ browser[32] ‖ u16be(len id) ‖ id
35
+ * ‖ u16be(count scope) ‖ ( u16be(len entry) ‖ entry )* ‖ u32be(valid_for)
36
+ *
37
+ * Throws on an unknown role, an empty or oversized audience, an audience or scope entry
38
+ * carrying a control character (U+0000–U+001F, U+007F) or a lone surrogate (not UTF-8), an
39
+ * empty scope entry, a browser key of the wrong size, an empty or oversized id, a validity
40
+ * that is not an integer in 1..=2^32-1, or a binding that would not fit the possession
41
+ * scheme's u16 field.
42
+ */
43
+ export declare function loginBinding(role: number, audience: string, req: LoginRequest): Uint8Array;
44
+ /**
45
+ * The person's login proof: possession by the key behind `seed`, in `LOGIN_DOMAIN`, over
46
+ * `req.nonce` and `loginBinding(LOGIN_ROLE_LOGIN, audience, req)`. Throws for what
47
+ * `loginBinding` throws, for a short nonce, and for a seed of the wrong size.
48
+ */
49
+ export declare function proveLogin(seed: Uint8Array, audience: string, req: LoginRequest): Uint8Array;
50
+ /**
51
+ * Whether `signature` is the login proof by the key behind `pub` for `req` at `audience`.
52
+ * Total: every shape failure is `false`.
53
+ */
54
+ export declare function verifyLogin(pub: Uint8Array, audience: string, req: LoginRequest, signature: Uint8Array): boolean;
55
+ /**
56
+ * The browser's collect proof: possession by the key behind `seed` — which must be the key
57
+ * `req` names as `browser` — in `LOGIN_DOMAIN`, over `req.nonce` and
58
+ * `loginBinding(LOGIN_ROLE_COLLECT, audience, req)`. A seed whose public key is not
59
+ * `req.browser` throws: the proof is only meaningful from the key the request names.
60
+ */
61
+ export declare function proveCollect(seed: Uint8Array, audience: string, req: LoginRequest): Uint8Array;
62
+ /** Whether `signature` is the collect proof by `req.browser` for `req` at `audience`. Total. */
63
+ export declare function verifyCollect(audience: string, req: LoginRequest, signature: Uint8Array): boolean;
@@ -0,0 +1,171 @@
1
+ // The archon login scheme — MAY THIS EPHEMERAL KEY ACT AS ME, HERE, FOR THIS, UNTIL THEN?
2
+ //
3
+ // A browser (any key-less client) holds an ephemeral key K and opens a request at a service;
4
+ // the person's CLI, holding P, proves to that service that P agrees to let K act there for a
5
+ // stated scope and validity. The proof is the possession scheme (./possession.ts) in the
6
+ // domain `LOGIN_DOMAIN`, over the server's nonce and a binding that names everything the
7
+ // person approved: the audience the CLI talks to, K, the request, the scope entries and the
8
+ // validity — see docs/login.md §3.
9
+ //
10
+ // THE AUDIENCE IS DERIVED, NEVER TRANSPORTED: the CLI takes it from the URL it was invoked
11
+ // with and the server from its own configuration (docs/login.md §2). This module takes it as
12
+ // an argument and binds it; it never reads it off a message. Everything this module refuses
13
+ // to source — the nonce, the id, K, clocks, the delegation's contents, custody, sockets — is
14
+ // an argument or another layer's (ADR 0004, ADR 0007).
15
+ //
16
+ // Two proofs share the layout and differ in a role byte: the login proof (0x01) is made by P;
17
+ // the collect proof (0x02) is made by K when the browser collects the answer, so a bystander
18
+ // who saw the request id cannot consume the login.
19
+ import { getPublicKey, PUBLIC_KEY_SIZE, SEED_SIZE } from "@bitspark/archon";
20
+ import { MAX_FIELD_SIZE as POSSESSION_MAX_FIELD_SIZE, provePossession, verifyPossession } from "./possession.js";
21
+ /** The RFC 8032 context every login-scheme proof is made in. */
22
+ export const LOGIN_DOMAIN = "archon-login/1";
23
+ /** The binding's first byte for the person's proof (made by P). */
24
+ export const LOGIN_ROLE_LOGIN = 0x01;
25
+ /** The binding's first byte for the browser's collect proof (made by K). */
26
+ export const LOGIN_ROLE_COLLECT = 0x02;
27
+ /**
28
+ * The longest audience, id or scope entry, in bytes — the u16 length prefix's bound. It also
29
+ * bounds the scope entry count and the whole binding (which must fit the possession scheme's
30
+ * own u16 field).
31
+ */
32
+ export const LOGIN_MAX_FIELD_SIZE = 0xffff;
33
+ const utf8 = new TextEncoder();
34
+ /**
35
+ * The bytes both proofs are bound to, for `role` over `audience` and `req` (docs/login.md
36
+ * §3.2):
37
+ *
38
+ * role ‖ u16be(len audience) ‖ audience ‖ browser[32] ‖ u16be(len id) ‖ id
39
+ * ‖ u16be(count scope) ‖ ( u16be(len entry) ‖ entry )* ‖ u32be(valid_for)
40
+ *
41
+ * Throws on an unknown role, an empty or oversized audience, an audience or scope entry
42
+ * carrying a control character (U+0000–U+001F, U+007F) or a lone surrogate (not UTF-8), an
43
+ * empty scope entry, a browser key of the wrong size, an empty or oversized id, a validity
44
+ * that is not an integer in 1..=2^32-1, or a binding that would not fit the possession
45
+ * scheme's u16 field.
46
+ */
47
+ export function loginBinding(role, audience, req) {
48
+ if (role !== LOGIN_ROLE_LOGIN && role !== LOGIN_ROLE_COLLECT)
49
+ throw new Error(`login: unknown role 0x${role.toString(16)}`);
50
+ const aud = checkText("audience", audience);
51
+ if (req.browser.length !== PUBLIC_KEY_SIZE) {
52
+ throw new Error(`login: browser key is ${req.browser.length} bytes, want ${PUBLIC_KEY_SIZE}`);
53
+ }
54
+ if (req.id.length === 0 || req.id.length > LOGIN_MAX_FIELD_SIZE) {
55
+ throw new Error(`login: id is ${req.id.length} bytes, want 1..=${LOGIN_MAX_FIELD_SIZE}`);
56
+ }
57
+ if (req.scope.length > LOGIN_MAX_FIELD_SIZE) {
58
+ throw new Error(`login: ${req.scope.length} scope entries, want at most ${LOGIN_MAX_FIELD_SIZE}`);
59
+ }
60
+ const entries = req.scope.map((entry, i) => checkText(`scope[${i}]`, entry));
61
+ if (!Number.isInteger(req.validFor) || req.validFor < 1 || req.validFor > 0xffffffff) {
62
+ throw new Error(`login: valid_for is ${req.validFor}, want an integer in 1..=4294967295`);
63
+ }
64
+ let size = 1 + 2 + aud.length + PUBLIC_KEY_SIZE + 2 + req.id.length + 2 + 4;
65
+ for (const e of entries)
66
+ size += 2 + e.length;
67
+ if (size > POSSESSION_MAX_FIELD_SIZE) {
68
+ throw new Error(`login: binding is ${size} bytes, over the possession scheme's ${POSSESSION_MAX_FIELD_SIZE}`);
69
+ }
70
+ const out = new Uint8Array(size);
71
+ let at = 0;
72
+ out[at++] = role;
73
+ at = putField(out, at, aud);
74
+ out.set(req.browser, at);
75
+ at += PUBLIC_KEY_SIZE;
76
+ at = putField(out, at, req.id);
77
+ out[at++] = (entries.length >> 8) & 0xff;
78
+ out[at++] = entries.length & 0xff;
79
+ for (const e of entries)
80
+ at = putField(out, at, e);
81
+ out[at++] = (req.validFor >>> 24) & 0xff;
82
+ out[at++] = (req.validFor >>> 16) & 0xff;
83
+ out[at++] = (req.validFor >>> 8) & 0xff;
84
+ out[at++] = req.validFor & 0xff;
85
+ return out;
86
+ }
87
+ /**
88
+ * The person's login proof: possession by the key behind `seed`, in `LOGIN_DOMAIN`, over
89
+ * `req.nonce` and `loginBinding(LOGIN_ROLE_LOGIN, audience, req)`. Throws for what
90
+ * `loginBinding` throws, for a short nonce, and for a seed of the wrong size.
91
+ */
92
+ export function proveLogin(seed, audience, req) {
93
+ if (seed.length !== SEED_SIZE)
94
+ throw new Error(`login: seed is ${seed.length} bytes, want ${SEED_SIZE}`);
95
+ return provePossession(seed, LOGIN_DOMAIN, req.nonce, loginBinding(LOGIN_ROLE_LOGIN, audience, req));
96
+ }
97
+ /**
98
+ * Whether `signature` is the login proof by the key behind `pub` for `req` at `audience`.
99
+ * Total: every shape failure is `false`.
100
+ */
101
+ export function verifyLogin(pub, audience, req, signature) {
102
+ let binding;
103
+ try {
104
+ binding = loginBinding(LOGIN_ROLE_LOGIN, audience, req);
105
+ }
106
+ catch {
107
+ return false;
108
+ }
109
+ return verifyPossession(pub, LOGIN_DOMAIN, req.nonce, binding, signature);
110
+ }
111
+ /**
112
+ * The browser's collect proof: possession by the key behind `seed` — which must be the key
113
+ * `req` names as `browser` — in `LOGIN_DOMAIN`, over `req.nonce` and
114
+ * `loginBinding(LOGIN_ROLE_COLLECT, audience, req)`. A seed whose public key is not
115
+ * `req.browser` throws: the proof is only meaningful from the key the request names.
116
+ */
117
+ export function proveCollect(seed, audience, req) {
118
+ if (seed.length !== SEED_SIZE)
119
+ throw new Error(`login: seed is ${seed.length} bytes, want ${SEED_SIZE}`);
120
+ const pub = getPublicKey(seed);
121
+ if (pub.length !== req.browser.length || !pub.every((b, i) => b === req.browser[i])) {
122
+ throw new Error("login: seed is not the browser key the request names");
123
+ }
124
+ return provePossession(seed, LOGIN_DOMAIN, req.nonce, loginBinding(LOGIN_ROLE_COLLECT, audience, req));
125
+ }
126
+ /** Whether `signature` is the collect proof by `req.browser` for `req` at `audience`. Total. */
127
+ export function verifyCollect(audience, req, signature) {
128
+ let binding;
129
+ try {
130
+ binding = loginBinding(LOGIN_ROLE_COLLECT, audience, req);
131
+ }
132
+ catch {
133
+ return false;
134
+ }
135
+ return verifyPossession(req.browser, LOGIN_DOMAIN, req.nonce, binding, signature);
136
+ }
137
+ /**
138
+ * The rule shared by the audience and every scope entry: 1..=LOGIN_MAX_FIELD_SIZE bytes of
139
+ * well-formed UTF-8 with no control character (U+0000–U+001F, U+007F). These bytes are
140
+ * displayed verbatim by the CLI before signing; a control character could make the display
141
+ * lie about what is bound. Returns the UTF-8 bytes.
142
+ */
143
+ function checkText(what, s) {
144
+ if (!isWellFormed(s))
145
+ throw new Error(`login: ${what} is not well-formed UTF-16 (a lone surrogate is not UTF-8)`);
146
+ for (const ch of s) {
147
+ const cp = ch.codePointAt(0);
148
+ if (cp < 0x20 || cp === 0x7f) {
149
+ throw new Error(`login: ${what} carries a control character U+${cp.toString(16).toUpperCase().padStart(4, "0")}`);
150
+ }
151
+ }
152
+ const bytes = utf8.encode(s);
153
+ if (bytes.length === 0 || bytes.length > LOGIN_MAX_FIELD_SIZE) {
154
+ throw new Error(`login: ${what} is ${bytes.length} bytes, want 1..=${LOGIN_MAX_FIELD_SIZE}`);
155
+ }
156
+ return bytes;
157
+ }
158
+ function isWellFormed(s) {
159
+ const native = s.isWellFormed;
160
+ if (typeof native === "function")
161
+ return native.call(s);
162
+ return !/[\uD800-\uDBFF](?![\uDC00-\uDFFF])|(?<![\uD800-\uDBFF])[\uDC00-\uDFFF]/.test(s);
163
+ }
164
+ /** Writes u16be(len field) ‖ field at `at`; returns the new offset. */
165
+ function putField(out, at, field) {
166
+ out[at++] = (field.length >> 8) & 0xff;
167
+ out[at++] = field.length & 0xff;
168
+ out.set(field, at);
169
+ return at + field.length;
170
+ }
171
+ //# sourceMappingURL=login.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"login.js","sourceRoot":"","sources":["../../src/login.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,20 @@
1
+ /** The first byte of every possession message. Distinct from the envelope's `SCHEME_TAG`. */
2
+ export declare const POSSESSION_SCHEME_TAG = 1;
3
+ /** The shortest nonce accepted, in bytes. Below this a proof is guessable, so it is refused. */
4
+ export declare const MIN_NONCE_SIZE = 16;
5
+ /** The longest nonce or binding, in bytes — the u16 length prefix's bound. */
6
+ export declare const MAX_FIELD_SIZE = 65535;
7
+ /**
8
+ * Prove possession of the key behind `seed` to a challenger who supplied `nonce` and
9
+ * `binding`, in `domain`. Throws on an invalid domain (see `signInDomain`), a nonce
10
+ * shorter than `MIN_NONCE_SIZE`, an empty binding, or either field over `MAX_FIELD_SIZE`.
11
+ */
12
+ export declare function provePossession(seed: Uint8Array, domain: string, nonce: Uint8Array, binding: Uint8Array): Uint8Array;
13
+ /**
14
+ * Verify a possession proof: `signature` was made by the key behind `pub` over this
15
+ * `nonce` and `binding` in `domain`. Total: every shape failure — bad domain, short
16
+ * nonce, empty binding, wrong-sized key or signature — is `false`.
17
+ */
18
+ export declare function verifyPossession(pub: Uint8Array, domain: string, nonce: Uint8Array, binding: Uint8Array, signature: Uint8Array): boolean;
19
+ /** The pinned layout of what gets signed. Exported so a consumer can pin it too. */
20
+ export declare function possessionMessageBytes(nonce: Uint8Array, binding: Uint8Array): Uint8Array;
@@ -0,0 +1,71 @@
1
+ // Proof of possession — CAN THEY SIGN, RIGHT NOW, FOR THIS CHANNEL?
2
+ //
3
+ // The challenger picks a `nonce` (fresh entropy, ≥ 16 bytes — its own, never this
4
+ // package's) and a `binding` (something only this channel has: a session key, a TLS
5
+ // exporter, the server's identity — the transport's, never this package's). The prover
6
+ // signs a fixed layout of both in the protocol's domain; the challenger verifies with the
7
+ // prover's public key.
8
+ //
9
+ // THE BINDING IS WHAT MAKES THIS A PROOF. A signed nonce alone is relayable: an attacker
10
+ // facing the server as the victim forwards the server's nonce to the victim under some
11
+ // pretext, gets it signed, and presents the signature. Bound to the channel, the
12
+ // signature is worthless anywhere else. So an empty binding is refused outright — the
13
+ // thing it would produce looks like a proof and is not one.
14
+ //
15
+ // The signed bytes are `SCHEME_TAG ‖ u16be(len nonce) ‖ nonce ‖ u16be(len binding) ‖
16
+ // binding`, signed with `signInDomain` in the caller's domain. The tag keeps a
17
+ // possession message and an envelope payload in the same domain from ever being the
18
+ // same bytes.
19
+ import { signInDomain, verifyInDomain } from "@bitspark/archon";
20
+ /** The first byte of every possession message. Distinct from the envelope's `SCHEME_TAG`. */
21
+ export const POSSESSION_SCHEME_TAG = 0x01;
22
+ /** The shortest nonce accepted, in bytes. Below this a proof is guessable, so it is refused. */
23
+ export const MIN_NONCE_SIZE = 16;
24
+ /** The longest nonce or binding, in bytes — the u16 length prefix's bound. */
25
+ export const MAX_FIELD_SIZE = 0xffff;
26
+ /**
27
+ * Prove possession of the key behind `seed` to a challenger who supplied `nonce` and
28
+ * `binding`, in `domain`. Throws on an invalid domain (see `signInDomain`), a nonce
29
+ * shorter than `MIN_NONCE_SIZE`, an empty binding, or either field over `MAX_FIELD_SIZE`.
30
+ */
31
+ export function provePossession(seed, domain, nonce, binding) {
32
+ return signInDomain(seed, domain, possessionMessageBytes(nonce, binding));
33
+ }
34
+ /**
35
+ * Verify a possession proof: `signature` was made by the key behind `pub` over this
36
+ * `nonce` and `binding` in `domain`. Total: every shape failure — bad domain, short
37
+ * nonce, empty binding, wrong-sized key or signature — is `false`.
38
+ */
39
+ export function verifyPossession(pub, domain, nonce, binding, signature) {
40
+ let message;
41
+ try {
42
+ message = possessionMessageBytes(nonce, binding);
43
+ }
44
+ catch {
45
+ return false;
46
+ }
47
+ return verifyInDomain(pub, domain, message, signature);
48
+ }
49
+ /** The pinned layout of what gets signed. Exported so a consumer can pin it too. */
50
+ export function possessionMessageBytes(nonce, binding) {
51
+ if (nonce.length < MIN_NONCE_SIZE)
52
+ throw new Error(`nonce is ${nonce.length} bytes, min ${MIN_NONCE_SIZE}`);
53
+ if (nonce.length > MAX_FIELD_SIZE)
54
+ throw new Error(`nonce is ${nonce.length} bytes, max ${MAX_FIELD_SIZE}`);
55
+ if (binding.length === 0)
56
+ throw new Error("binding is empty — an unbound proof is not a proof");
57
+ if (binding.length > MAX_FIELD_SIZE)
58
+ throw new Error(`binding is ${binding.length} bytes, max ${MAX_FIELD_SIZE}`);
59
+ const out = new Uint8Array(1 + 2 + nonce.length + 2 + binding.length);
60
+ let at = 0;
61
+ out[at++] = POSSESSION_SCHEME_TAG;
62
+ out[at++] = nonce.length >> 8;
63
+ out[at++] = nonce.length & 0xff;
64
+ out.set(nonce, at);
65
+ at += nonce.length;
66
+ out[at++] = binding.length >> 8;
67
+ out[at++] = binding.length & 0xff;
68
+ out.set(binding, at);
69
+ return out;
70
+ }
71
+ //# sourceMappingURL=possession.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"possession.js","sourceRoot":"","sources":["../../src/possession.ts"],"names":[],"mappings":"AAAA,oEAAoE;AACpE,EAAE;AACF,kFAAkF;AAClF,oFAAoF;AACpF,uFAAuF;AACvF,0FAA0F;AAC1F,uBAAuB;AACvB,EAAE;AACF,yFAAyF;AACzF,uFAAuF;AACvF,iFAAiF;AACjF,sFAAsF;AACtF,4DAA4D;AAC5D,EAAE;AACF,qFAAqF;AACrF,+EAA+E;AAC/E,oFAAoF;AACpF,cAAc;AACd,OAAO,EAAE,YAAY,EAAE,cAAc,EAAE,MAAM,kBAAkB,CAAC;AAEhE,6FAA6F;AAC7F,MAAM,CAAC,MAAM,qBAAqB,GAAG,IAAI,CAAC;AAC1C,gGAAgG;AAChG,MAAM,CAAC,MAAM,cAAc,GAAG,EAAE,CAAC;AACjC,8EAA8E;AAC9E,MAAM,CAAC,MAAM,cAAc,GAAG,MAAM,CAAC;AAErC;;;;GAIG;AACH,MAAM,UAAU,eAAe,CAC7B,IAAgB,EAChB,MAAc,EACd,KAAiB,EACjB,OAAmB;IAEnB,OAAO,YAAY,CAAC,IAAI,EAAE,MAAM,EAAE,sBAAsB,CAAC,KAAK,EAAE,OAAO,CAAC,CAAC,CAAC;AAC5E,CAAC;AAED;;;;GAIG;AACH,MAAM,UAAU,gBAAgB,CAC9B,GAAe,EACf,MAAc,EACd,KAAiB,EACjB,OAAmB,EACnB,SAAqB;IAErB,IAAI,OAAmB,CAAC;IACxB,IAAI,CAAC;QACH,OAAO,GAAG,sBAAsB,CAAC,KAAK,EAAE,OAAO,CAAC,CAAC;IACnD,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,KAAK,CAAC;IACf,CAAC;IACD,OAAO,cAAc,CAAC,GAAG,EAAE,MAAM,EAAE,OAAO,EAAE,SAAS,CAAC,CAAC;AACzD,CAAC;AAED,oFAAoF;AACpF,MAAM,UAAU,sBAAsB,CAAC,KAAiB,EAAE,OAAmB;IAC3E,IAAI,KAAK,CAAC,MAAM,GAAG,cAAc;QAAE,MAAM,IAAI,KAAK,CAAC,YAAY,KAAK,CAAC,MAAM,eAAe,cAAc,EAAE,CAAC,CAAC;IAC5G,IAAI,KAAK,CAAC,MAAM,GAAG,cAAc;QAAE,MAAM,IAAI,KAAK,CAAC,YAAY,KAAK,CAAC,MAAM,eAAe,cAAc,EAAE,CAAC,CAAC;IAC5G,IAAI,OAAO,CAAC,MAAM,KAAK,CAAC;QAAE,MAAM,IAAI,KAAK,CAAC,oDAAoD,CAAC,CAAC;IAChG,IAAI,OAAO,CAAC,MAAM,GAAG,cAAc;QAAE,MAAM,IAAI,KAAK,CAAC,cAAc,OAAO,CAAC,MAAM,eAAe,cAAc,EAAE,CAAC,CAAC;IAClH,MAAM,GAAG,GAAG,IAAI,UAAU,CAAC,CAAC,GAAG,CAAC,GAAG,KAAK,CAAC,MAAM,GAAG,CAAC,GAAG,OAAO,CAAC,MAAM,CAAC,CAAC;IACtE,IAAI,EAAE,GAAG,CAAC,CAAC;IACX,GAAG,CAAC,EAAE,EAAE,CAAC,GAAG,qBAAqB,CAAC;IAClC,GAAG,CAAC,EAAE,EAAE,CAAC,GAAG,KAAK,CAAC,MAAM,IAAI,CAAC,CAAC;IAC9B,GAAG,CAAC,EAAE,EAAE,CAAC,GAAG,KAAK,CAAC,MAAM,GAAG,IAAI,CAAC;IAChC,GAAG,CAAC,GAAG,CAAC,KAAK,EAAE,EAAE,CAAC,CAAC;IACnB,EAAE,IAAI,KAAK,CAAC,MAAM,CAAC;IACnB,GAAG,CAAC,EAAE,EAAE,CAAC,GAAG,OAAO,CAAC,MAAM,IAAI,CAAC,CAAC;IAChC,GAAG,CAAC,EAAE,EAAE,CAAC,GAAG,OAAO,CAAC,MAAM,GAAG,IAAI,CAAC;IAClC,GAAG,CAAC,GAAG,CAAC,OAAO,EAAE,EAAE,CAAC,CAAC;IACrB,OAAO,GAAG,CAAC;AACb,CAAC"}
package/package.json ADDED
@@ -0,0 +1,30 @@
1
+ {
2
+ "name": "@bitspark/archon-sdk",
3
+ "version": "0.6.1",
4
+ "description": "archon-sdk: proof of possession and the signed envelope, one layer above the identity floor. Deterministic given its inputs; no RNG, no clock, no socket.",
5
+ "license": "Apache-2.0",
6
+ "repository": {
7
+ "type": "git",
8
+ "url": "https://github.com/Bitspark/archon"
9
+ },
10
+ "type": "module",
11
+ "main": "dist/src/index.js",
12
+ "types": "dist/src/index.d.ts",
13
+ "files": [
14
+ "dist/src"
15
+ ],
16
+ "publishConfig": {
17
+ "registry": "https://registry.npmjs.org"
18
+ },
19
+ "scripts": {
20
+ "build": "tsc -p tsconfig.json",
21
+ "test": "tsc -p tsconfig.json && node --test dist/test/*.test.js"
22
+ },
23
+ "dependencies": {
24
+ "@bitspark/archon": "^0.6.1"
25
+ },
26
+ "devDependencies": {
27
+ "@types/node": "^22",
28
+ "typescript": "^5.7"
29
+ }
30
+ }