sliftutils 1.7.144 → 1.7.145

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/index.d.ts CHANGED
@@ -417,7 +417,6 @@ declare module "sliftutils/misc/random" {
417
417
  export declare function getSeededRandom(seed: number): () => number;
418
418
  export declare function shuffle<T>(array: T[], seed: number): T[];
419
419
  export declare function secureRandom(): number;
420
- export declare function secureRandomHex(byteLength: number): string;
421
420
 
422
421
  }
423
422
 
package/misc/random.d.ts CHANGED
@@ -1,4 +1,3 @@
1
1
  export declare function getSeededRandom(seed: number): () => number;
2
2
  export declare function shuffle<T>(array: T[], seed: number): T[];
3
3
  export declare function secureRandom(): number;
4
- export declare function secureRandomHex(byteLength: number): string;
package/misc/random.ts CHANGED
@@ -43,13 +43,3 @@ export function secureRandom(): number {
43
43
  }
44
44
  return randomDataF64[0];
45
45
  }
46
-
47
- export function secureRandomHex(byteLength: number): string {
48
- let data = new Uint8Array(byteLength);
49
- if (!isNode()) {
50
- window.crypto.getRandomValues(data);
51
- } else {
52
- crypto.getRandomValues(data);
53
- }
54
- return Array.from(data).map(x => x.toString(16).padStart(2, "0")).join("");
55
- }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "sliftutils",
3
- "version": "1.7.144",
3
+ "version": "1.7.145",
4
4
  "main": "index.js",
5
5
  "license": "MIT",
6
6
  "files": [
@@ -18,36 +18,14 @@
18
18
 
19
19
  import sha256 from "js-sha256";
20
20
  import { isNode } from "socket-function/src/misc";
21
- import { secureRandomHex } from "../../misc/random";
22
21
  import { getCommonName, getIdentityCA, getMachineId, getThreadKeyCert, sign, validateCertificate, verify } from "../../misc/https/certs";
23
22
  import { getTrustedMachines } from "./machines";
24
23
 
25
- const MAX_SIGNED_AGE = 5 * 60 * 1000;
26
- // Clocks disagree, so a payload from slightly ahead is normal - but one from far ahead would
27
- // never age out, and so would never stop being accepted.
28
- const MAX_SIGNED_FUTURE = 60 * 60 * 1000;
29
- const NONCE_BYTES = 16;
30
- // Nonces of the requests we have accepted, so none of them can be replayed. Cleared wholesale at
31
- // the limit rather than expired one by one: reaching it takes a million signed requests inside
32
- // the five minute window, which is a server being hammered - something we would notice for other
33
- // reasons - and all it buys is one replay of one request.
34
- const MAX_SEEN_NONCES = 1000 * 1000;
35
- let seenNonces = new Set<string>();
36
-
37
24
  export type SignedRequest<T> = {
38
25
  signature: string;
39
26
  payload: {
40
- // When it was signed - anything older than MAX_SIGNED_AGE is refused
41
- time: number;
42
- // Who the sender believes it is talking to: an IP, a machineId, a threadId - whatever
43
- // both sides use to name the server. Being inside the signed payload is what stops a
44
- // machine in the middle: a request addressed to someone else cannot be replayed at the
45
- // real server, and the middleman cannot write requests of its own because it is not a
46
- // trusted machine.
27
+ // The ip we are talking to.
47
28
  targetId: string;
48
- targetIsMachineId?: boolean;
49
- // Makes the request one use only - the server refuses a nonce it has already accepted
50
- nonce: string;
51
29
  // The thread certificate that signed this
52
30
  cert: string;
53
31
  // The machine CA that issued cert - the machineId comes from its common name
@@ -59,7 +37,6 @@ export type SignedRequest<T> = {
59
37
  export type SignedReply = {
60
38
  signature: string;
61
39
  payload: {
62
- time: number;
63
40
  // The request this reply answers, with the data collapsed to its hash - resending the
64
41
  // data would say nothing the hash does not
65
42
  request: Omit<SignedRequest<unknown>["payload"], "data"> & { dataHash: string };
@@ -72,25 +49,11 @@ function dataHash(data: unknown) {
72
49
  return sha256.sha256(JSON.stringify(data));
73
50
  }
74
51
 
75
- function assertSignedTime(kind: string, time: number) {
76
- let oldest = Date.now() - MAX_SIGNED_AGE;
77
- if (time < oldest) {
78
- throw new Error(`Signed ${kind} is too old, ${time} < ${oldest}`);
79
- }
80
- let newest = Date.now() + MAX_SIGNED_FUTURE;
81
- if (time > newest) {
82
- throw new Error(`Signed ${kind} is too far in the future, ${time} > ${newest}`);
83
- }
84
- }
85
-
86
- export function signRequest<T>(domain: string, config: { targetId: string; targetIsMachineId?: boolean; data: T }): SignedRequest<T> {
52
+ export function signRequest<T>(domain: string, config: { targetId: string; data: T }): SignedRequest<T> {
87
53
  let threadKeyCert = getThreadKeyCert(domain);
88
54
  let issuer = getIdentityCA(domain);
89
55
  let payload: SignedRequest<T>["payload"] = {
90
- time: Date.now(),
91
56
  targetId: config.targetId,
92
- targetIsMachineId: config.targetIsMachineId,
93
- nonce: secureRandomHex(NONCE_BYTES),
94
57
  cert: threadKeyCert.cert.toString(),
95
58
  certIssuer: issuer.cert.toString(),
96
59
  data: config.data,
@@ -99,11 +62,10 @@ export function signRequest<T>(domain: string, config: { targetId: string; targe
99
62
  }
100
63
 
101
64
  /** Returns the machineId that signed, the data, and the signed reply to send back - always
102
- produced, so the caller's only job is to return it. Throws if the signature, certificate
103
- chain, or time do not check out, or if the request was addressed to someone we are not:
104
- ownIdentities is every name this server answers to - its IPs (there are always several,
105
- internal and external), its machineId, its threadId - and the signed targetId must be one of
106
- them.
65
+ produced, so the caller's only job is to return it. Throws if the signature or certificate
66
+ chain do not check out, or if the request was addressed to someone we are not: ownIdentities
67
+ is every address this server answers to - there are always several, internal and external -
68
+ and the signed targetId must be one of them.
107
69
 
108
70
  IMPORTANT! You still need to check the machine with isMachineAccepted if you want to know if
109
71
  the machine is trusted. We just verify that they are who they say they are, not that who they
@@ -114,26 +76,16 @@ export function verifyRequest<T>(domain: string, signed: SignedRequest<T>, ownId
114
76
  reply: SignedReply;
115
77
  } {
116
78
  let { signature, payload } = signed;
117
- assertSignedTime("request", payload.time);
118
79
  verify(payload.cert, signature, payload);
119
80
  validateCertificate(domain, payload.cert, payload.certIssuer);
120
81
  if (!ownIdentities.includes(payload.targetId)) {
121
82
  throw new Error(`Request is for someone else. It is addressed to ${payload.targetId}, we are ${ownIdentities.join(", ")}`);
122
83
  }
123
- // After the signature, so only a real signer can take up a slot
124
- if (seenNonces.has(payload.nonce)) {
125
- throw new Error(`Request ${payload.nonce} was already used, so it is being refused as a replay`);
126
- }
127
- if (seenNonces.size >= MAX_SEEN_NONCES) {
128
- seenNonces.clear();
129
- }
130
- seenNonces.add(payload.nonce);
131
84
  let machineId = getMachineId(getCommonName(payload.certIssuer), domain);
132
85
  let threadKeyCert = getThreadKeyCert(domain);
133
86
  let issuer = getIdentityCA(domain);
134
87
  let { data: requestData, ...requestRest } = payload;
135
88
  let replyPayload: SignedReply["payload"] = {
136
- time: Date.now(),
137
89
  request: { ...requestRest, dataHash: dataHash(requestData) },
138
90
  cert: threadKeyCert.cert.toString(),
139
91
  certIssuer: issuer.cert.toString(),
@@ -145,14 +97,12 @@ export function verifyRequest<T>(domain: string, signed: SignedRequest<T>, ownId
145
97
  };
146
98
  }
147
99
 
148
- /** Returns the machineId that replied. Throws if the signature, certificate chain, or time do
149
- not check out, or if the reply does not answer the request we actually sent. When the request
150
- said targetIsMachineId, the machine that replied must be the machine the request was
151
- addressed to. On node the replying machine must also be one of the trusted machines - the
152
- browser has no machine list, so there that check is skipped. */
100
+ /** Returns the machineId that replied. Throws if the signature or certificate chain do not check
101
+ out, or if the reply does not answer the request we actually sent. On node the replying
102
+ machine must also be one of the trusted machines - the browser has no machine list, so there
103
+ that check is skipped. */
153
104
  export async function verifyReply(domain: string, request: SignedRequest<unknown>, reply: SignedReply): Promise<{ machineId: string }> {
154
105
  let { signature, payload } = reply;
155
- assertSignedTime("reply", payload.time);
156
106
  verify(payload.cert, signature, payload);
157
107
  validateCertificate(domain, payload.cert, payload.certIssuer);
158
108
  let { data: requestData, ...requestRest } = request.payload;
@@ -161,9 +111,6 @@ export async function verifyReply(domain: string, request: SignedRequest<unknown
161
111
  throw new Error(`Reply answers a different request than the one we sent`);
162
112
  }
163
113
  let machineId = getMachineId(getCommonName(payload.certIssuer), domain);
164
- if (request.payload.targetIsMachineId && machineId !== request.payload.targetId) {
165
- throw new Error(`Reply is from machine ${machineId}, but the request was addressed to machine ${request.payload.targetId}`);
166
- }
167
114
  if (isNode()) {
168
115
  if (!(await getTrustedMachines()).some(machine => machine.machineId === machineId)) {
169
116
  throw new Error(`Reply is signed by ${machineId}, which is not a trusted machine`);