sliftutils 1.7.141 → 1.7.142

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "sliftutils",
3
- "version": "1.7.141",
3
+ "version": "1.7.142",
4
4
  "main": "index.js",
5
5
  "license": "MIT",
6
6
  "files": [
@@ -5,36 +5,32 @@ import { getTrustedMachines } from "./machines";
5
5
 
6
6
  const MAX_SIGNED_AGE = 5 * 60 * 1000;
7
7
 
8
- // A request says who it is from AND who it believes it is talking to. The "to" is whatever both
9
- // sides use to identify the server - an IP, a machineId, a threadId - and being inside the signed
10
- // payload is what stops a machine in the middle: a request addressed to someone else cannot be
11
- // replayed at the real server, and the middleman cannot write requests of its own because it is
12
- // not a trusted machine.
13
8
  export type SignedRequest<T> = {
14
9
  signature: string;
15
10
  payload: {
11
+ // When it was signed - anything older than MAX_SIGNED_AGE is refused
16
12
  time: number;
17
- to: string;
13
+ // Who the sender believes it is talking to: an IP, a machineId, a threadId - whatever
14
+ // both sides use to name the server. Being inside the signed payload is what stops a
15
+ // machine in the middle: a request addressed to someone else cannot be replayed at the
16
+ // real server, and the middleman cannot write requests of its own because it is not a
17
+ // trusted machine.
18
+ targetId: string;
19
+ // The thread certificate that signed this
18
20
  cert: string;
21
+ // The machine CA that issued cert - the machineId comes from its common name
19
22
  certIssuer: string;
20
23
  data: T;
21
24
  };
22
25
  };
23
26
 
24
- // The reply echoes the request it answers, with the data collapsed to its hash - resending the
25
- // data would say nothing the hash does not. Signed by the replying thread's certificate, which
26
- // chains to its machine CA, which is where the machineId comes from.
27
27
  export type SignedReply<T> = {
28
28
  signature: string;
29
29
  payload: {
30
30
  time: number;
31
- request: {
32
- time: number;
33
- to: string;
34
- cert: string;
35
- certIssuer: string;
36
- dataHash: string;
37
- };
31
+ // The request this reply answers, with the data collapsed to its hash - resending the
32
+ // data would say nothing the hash does not
33
+ request: Omit<SignedRequest<unknown>["payload"], "data"> & { dataHash: string };
38
34
  cert: string;
39
35
  certIssuer: string;
40
36
  data: T;
@@ -45,12 +41,12 @@ function dataHash(data: unknown) {
45
41
  return sha256.sha256(JSON.stringify(data));
46
42
  }
47
43
 
48
- export function signRequest<T>(domain: string, config: { to: string; data: T }): SignedRequest<T> {
44
+ export function signRequest<T>(domain: string, config: { targetId: string; data: T }): SignedRequest<T> {
49
45
  let threadKeyCert = getThreadKeyCert(domain);
50
46
  let issuer = getIdentityCA(domain);
51
- let payload = {
47
+ let payload: SignedRequest<T>["payload"] = {
52
48
  time: Date.now(),
53
- to: config.to,
49
+ targetId: config.targetId,
54
50
  cert: threadKeyCert.cert.toString(),
55
51
  certIssuer: issuer.cert.toString(),
56
52
  data: config.data,
@@ -58,11 +54,17 @@ export function signRequest<T>(domain: string, config: { to: string; data: T }):
58
54
  return { signature: sign(threadKeyCert, payload), payload };
59
55
  }
60
56
 
61
- /** Returns the machineId that signed, and the data. Throws if the signature, certificate chain,
62
- or time do not check out, or if the request was addressed to someone we are not: ownIdentities
63
- is every name this server answers to - its IPs (there are always several, internal and
64
- external), its machineId, its threadId - and the signed "to" must be one of them. */
65
- export function verifyRequest<T>(domain: string, signed: SignedRequest<T>, ownIdentities: string[]): { machineId: string; data: T } {
57
+ /** Returns the machineId that signed, the data, and reply - which signs a reply to exactly this
58
+ request, so a reply cannot be produced for a request that was never verified. Throws if the
59
+ signature, certificate chain, or time do not check out, or if the request was addressed to
60
+ someone we are not: ownIdentities is every name this server answers to - its IPs (there are
61
+ always several, internal and external), its machineId, its threadId - and the signed targetId
62
+ must be one of them. */
63
+ export function verifyRequest<T>(domain: string, signed: SignedRequest<T>, ownIdentities: string[]): {
64
+ machineId: string;
65
+ data: T;
66
+ reply<R>(data: R): SignedReply<R>;
67
+ } {
66
68
  let { signature, payload } = signed;
67
69
  let signedThreshold = Date.now() - MAX_SIGNED_AGE;
68
70
  if (payload.time < signedThreshold) {
@@ -70,30 +72,27 @@ export function verifyRequest<T>(domain: string, signed: SignedRequest<T>, ownId
70
72
  }
71
73
  verify(payload.cert, signature, payload);
72
74
  validateCertificate(domain, payload.cert, payload.certIssuer);
73
- if (!ownIdentities.includes(payload.to)) {
74
- throw new Error(`Request is for someone else. It is addressed to ${payload.to}, we are ${ownIdentities.join(", ")}`);
75
+ if (!ownIdentities.includes(payload.targetId)) {
76
+ throw new Error(`Request is for someone else. It is addressed to ${payload.targetId}, we are ${ownIdentities.join(", ")}`);
75
77
  }
76
78
  let machineId = getMachineId(getCommonName(payload.certIssuer), domain);
77
- return { machineId, data: payload.data };
78
- }
79
-
80
- export function signReply<T>(domain: string, request: SignedRequest<unknown>, data: T): SignedReply<T> {
81
- let threadKeyCert = getThreadKeyCert(domain);
82
- let issuer = getIdentityCA(domain);
83
- let payload = {
84
- time: Date.now(),
85
- request: {
86
- time: request.payload.time,
87
- to: request.payload.to,
88
- cert: request.payload.cert,
89
- certIssuer: request.payload.certIssuer,
90
- dataHash: dataHash(request.payload.data),
79
+ return {
80
+ machineId,
81
+ data: payload.data,
82
+ reply<R>(data: R): SignedReply<R> {
83
+ let threadKeyCert = getThreadKeyCert(domain);
84
+ let issuer = getIdentityCA(domain);
85
+ let { data: requestData, ...requestRest } = payload;
86
+ let replyPayload: SignedReply<R>["payload"] = {
87
+ time: Date.now(),
88
+ request: { ...requestRest, dataHash: dataHash(requestData) },
89
+ cert: threadKeyCert.cert.toString(),
90
+ certIssuer: issuer.cert.toString(),
91
+ data,
92
+ };
93
+ return { signature: sign(threadKeyCert, replyPayload), payload: replyPayload };
91
94
  },
92
- cert: threadKeyCert.cert.toString(),
93
- certIssuer: issuer.cert.toString(),
94
- data,
95
95
  };
96
- return { signature: sign(threadKeyCert, payload), payload };
97
96
  }
98
97
 
99
98
  /** Returns the machineId that replied, and the data. Throws if the signature, certificate chain,
@@ -108,13 +107,9 @@ export async function verifyReply<T>(domain: string, request: SignedRequest<unkn
108
107
  }
109
108
  verify(payload.cert, signature, payload);
110
109
  validateCertificate(domain, payload.cert, payload.certIssuer);
111
- let echoed = payload.request;
112
- if (echoed.time !== request.payload.time
113
- || echoed.to !== request.payload.to
114
- || echoed.cert !== request.payload.cert
115
- || echoed.certIssuer !== request.payload.certIssuer
116
- || echoed.dataHash !== dataHash(request.payload.data)
117
- ) {
110
+ let { data: requestData, ...requestRest } = request.payload;
111
+ let { dataHash: echoedHash, ...echoedRest } = payload.request;
112
+ if (echoedHash !== dataHash(requestData) || JSON.stringify(echoedRest) !== JSON.stringify(requestRest)) {
118
113
  throw new Error(`Reply answers a different request than the one we sent`);
119
114
  }
120
115
  let machineId = getMachineId(getCommonName(payload.certIssuer), domain);