@libp2p/pnet 3.0.27-d5534174d → 3.0.28
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/dist/index.min.js +2 -2
- package/dist/index.min.js.map +3 -3
- package/dist/src/crypto.d.ts +19 -0
- package/dist/src/crypto.d.ts.map +1 -1
- package/dist/src/crypto.js +132 -8
- package/dist/src/crypto.js.map +1 -1
- package/dist/typedoc-urls.json +9 -0
- package/package.json +3 -3
- package/src/crypto.ts +151 -9
package/dist/src/crypto.d.ts
CHANGED
|
@@ -12,7 +12,26 @@ export declare class BoxMessageStream extends AbstractMultiaddrConnection {
|
|
|
12
12
|
private maConn;
|
|
13
13
|
private inboundXor;
|
|
14
14
|
private outboundXor;
|
|
15
|
+
private inboundReleased;
|
|
16
|
+
private outboundReleased;
|
|
17
|
+
private readonly onInboundMessage;
|
|
15
18
|
constructor(init: BoxMessageStreamInit);
|
|
19
|
+
/**
|
|
20
|
+
* A connection can close while still holding bytes it received. Decrypt them
|
|
21
|
+
* into our own read buffer first, so closing does not discard them and the
|
|
22
|
+
* application can still read them
|
|
23
|
+
*/
|
|
24
|
+
private drainMaConn;
|
|
25
|
+
/**
|
|
26
|
+
* Return the inbound cipher's slot in the shared wasm memory to the pool and
|
|
27
|
+
* zero the key material it holds. If that fails the slot leaks and the key
|
|
28
|
+
* stays resident.
|
|
29
|
+
*
|
|
30
|
+
* 'end' waits for the read buffer to drain, so a connection left paused with
|
|
31
|
+
* unread bytes never emits it and keeps its slot until the process exits
|
|
32
|
+
*/
|
|
33
|
+
private releaseInbound;
|
|
34
|
+
private releaseOutbound;
|
|
16
35
|
sendClose(options?: AbortOptions): Promise<void>;
|
|
17
36
|
sendData(data: Uint8ArrayList): SendResult;
|
|
18
37
|
sendReset(err: Error): void;
|
package/dist/src/crypto.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"crypto.d.ts","sourceRoot":"","sources":["../../src/crypto.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,2BAA2B,EAAE,MAAM,eAAe,CAAA;AAM3D,OAAO,KAAK,EAAE,YAAY,EAAE,mBAAmB,
|
|
1
|
+
{"version":3,"file":"crypto.d.ts","sourceRoot":"","sources":["../../src/crypto.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,2BAA2B,EAAE,MAAM,eAAe,CAAA;AAM3D,OAAO,KAAK,EAAE,YAAY,EAAE,mBAAmB,EAAsB,MAAM,mBAAmB,CAAA;AAC9F,OAAO,KAAK,EAAE,iBAAiB,EAAE,UAAU,EAAE,MAAM,eAAe,CAAA;AAClE,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,gBAAgB,CAAA;AAsBpD,MAAM,WAAW,oBAAqB,SAAQ,iBAAiB;IAC7D,MAAM,EAAE,mBAAmB,CAAA;IAC3B,UAAU,EAAE,UAAU,CAAA;IACtB,WAAW,EAAE,UAAU,CAAA;IACvB,GAAG,EAAE,UAAU,CAAA;CAChB;AAED,qBAAa,gBAAiB,SAAQ,2BAA2B;IAC/D,OAAO,CAAC,MAAM,CAAqB;IACnC,OAAO,CAAC,UAAU,CAAc;IAChC,OAAO,CAAC,WAAW,CAAc;IACjC,OAAO,CAAC,eAAe,CAAS;IAChC,OAAO,CAAC,gBAAgB,CAAS;IACjC,OAAO,CAAC,QAAQ,CAAC,gBAAgB,CAAmC;gBAEvD,IAAI,EAAE,oBAAoB;IAmGvC;;;;OAIG;IACH,OAAO,CAAC,WAAW;IAYnB;;;;;;;OAOG;IACH,OAAO,CAAC,cAAc;IAgBtB,OAAO,CAAC,eAAe;IAcjB,SAAS,CAAE,OAAO,CAAC,EAAE,YAAY,GAAG,OAAO,CAAC,IAAI,CAAC;IAIvD,QAAQ,CAAE,IAAI,EAAE,cAAc,GAAG,UAAU;IAO3C,SAAS,CAAE,GAAG,EAAE,KAAK,GAAG,IAAI;IAI5B,SAAS,IAAK,IAAI;IAIlB,UAAU,IAAK,IAAI;CASpB;AAED;;GAEG;AACH,wBAAgB,WAAW,CAAE,SAAS,EAAE,UAAU,GAAG;IAAE,GAAG,EAAE,MAAM,GAAG,SAAS,CAAC;IAAC,SAAS,EAAE,MAAM,GAAG,SAAS,CAAC;IAAC,GAAG,EAAE,UAAU,CAAA;CAAE,CAyB/H"}
|
package/dist/src/crypto.js
CHANGED
|
@@ -4,10 +4,28 @@ import { toString as uint8ArrayToString } from 'uint8arrays/to-string';
|
|
|
4
4
|
import xsalsa20 from 'xsalsa20';
|
|
5
5
|
import * as Errors from "./errors.js";
|
|
6
6
|
import { KEY_LENGTH } from "./key-generator.js";
|
|
7
|
+
const XOR_CHUNK_SIZE = 65536;
|
|
8
|
+
/**
|
|
9
|
+
* The xsalsa20 wasm backend copies each input into linear memory, which starts
|
|
10
|
+
* at 640KiB and is capped at 62.5MiB. Growing it detaches every other cipher's
|
|
11
|
+
* view of it, so keep chunks under the initial size rather than the cap. The
|
|
12
|
+
* cipher resumes where it left off, so the result matches a single update.
|
|
13
|
+
*/
|
|
14
|
+
function xorInChunks(xor, input) {
|
|
15
|
+
const output = new Uint8Array(input.byteLength);
|
|
16
|
+
for (let start = 0; start < input.byteLength; start += XOR_CHUNK_SIZE) {
|
|
17
|
+
const end = Math.min(start + XOR_CHUNK_SIZE, input.byteLength);
|
|
18
|
+
xor.update(input.subarray(start, end), output.subarray(start, end));
|
|
19
|
+
}
|
|
20
|
+
return output;
|
|
21
|
+
}
|
|
7
22
|
export class BoxMessageStream extends AbstractMultiaddrConnection {
|
|
8
23
|
maConn;
|
|
9
24
|
inboundXor;
|
|
10
25
|
outboundXor;
|
|
26
|
+
inboundReleased;
|
|
27
|
+
outboundReleased;
|
|
28
|
+
onInboundMessage;
|
|
11
29
|
constructor(init) {
|
|
12
30
|
super({
|
|
13
31
|
...init,
|
|
@@ -16,22 +34,46 @@ export class BoxMessageStream extends AbstractMultiaddrConnection {
|
|
|
16
34
|
});
|
|
17
35
|
this.inboundXor = xsalsa20(init.remoteNonce, init.psk);
|
|
18
36
|
this.outboundXor = xsalsa20(init.localNonce, init.psk);
|
|
37
|
+
this.inboundReleased = false;
|
|
38
|
+
this.outboundReleased = false;
|
|
19
39
|
this.maConn = init.maConn;
|
|
20
|
-
this.
|
|
40
|
+
this.onInboundMessage = (evt) => {
|
|
21
41
|
const data = evt.data;
|
|
22
|
-
|
|
23
|
-
|
|
42
|
+
// a peer can send data after closing and nothing upstream rejects it
|
|
43
|
+
if (this.inboundReleased) {
|
|
44
|
+
this.log('discarding %d bytes received after the readable end ended', data.byteLength);
|
|
45
|
+
return;
|
|
24
46
|
}
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
this.onData(this.inboundXor
|
|
47
|
+
try {
|
|
48
|
+
if (data instanceof Uint8Array) {
|
|
49
|
+
this.onData(xorInChunks(this.inboundXor, data));
|
|
50
|
+
}
|
|
51
|
+
else {
|
|
52
|
+
for (const buf of data) {
|
|
53
|
+
// onData dispatches synchronously, so the consumer can abort and
|
|
54
|
+
// release the cipher part way through the message
|
|
55
|
+
if (this.inboundReleased) {
|
|
56
|
+
break;
|
|
57
|
+
}
|
|
58
|
+
this.onData(xorInChunks(this.inboundXor, buf));
|
|
59
|
+
}
|
|
28
60
|
}
|
|
29
61
|
}
|
|
30
|
-
|
|
62
|
+
catch (err) {
|
|
63
|
+
// any bytes we fail to process leave the cipher permanently out of
|
|
64
|
+
// step with the remote so tear the connection down
|
|
65
|
+
this.log.error('error decrypting inbound data - %e', err);
|
|
66
|
+
this.abort(err);
|
|
67
|
+
}
|
|
68
|
+
};
|
|
69
|
+
this.maConn.addEventListener('message', this.onInboundMessage);
|
|
31
70
|
// resume sending when the underlying connection can accept more data
|
|
32
71
|
this.maConn.addEventListener('drain', () => {
|
|
33
72
|
this.onMuxerDrain();
|
|
34
73
|
});
|
|
74
|
+
this.maConn.addEventListener('end', () => {
|
|
75
|
+
this.releaseInbound();
|
|
76
|
+
});
|
|
35
77
|
this.maConn.addEventListener('close', (evt) => {
|
|
36
78
|
if (evt.error != null) {
|
|
37
79
|
if (evt.local) {
|
|
@@ -40,11 +82,88 @@ export class BoxMessageStream extends AbstractMultiaddrConnection {
|
|
|
40
82
|
else {
|
|
41
83
|
this.onRemoteReset();
|
|
42
84
|
}
|
|
85
|
+
// an errored connection never delivers what it still holds, so waiting
|
|
86
|
+
// for 'end' would keep the cipher forever
|
|
87
|
+
this.releaseInbound();
|
|
43
88
|
}
|
|
44
89
|
else {
|
|
90
|
+
// the two ends have to close at different moments. the write side
|
|
91
|
+
// first, because the drain dispatches to the consumer and a reply
|
|
92
|
+
// would otherwise reach a released cipher and abort a graceful close.
|
|
93
|
+
// the read side after, because closing it while our buffer is still
|
|
94
|
+
// empty makes onData discard everything the drain produces
|
|
95
|
+
this.writeStatus = 'closed';
|
|
96
|
+
this.drainMaConn();
|
|
45
97
|
this.onTransportClosed();
|
|
46
98
|
}
|
|
99
|
+
// last, the branches above close the write side first and that is what
|
|
100
|
+
// stops sendData reaching a released cipher
|
|
101
|
+
this.releaseOutbound();
|
|
47
102
|
});
|
|
103
|
+
// the nonce handshake reads from the connection before this stream exists,
|
|
104
|
+
// so it can already have ended or closed. bytes unwrapped back onto it are
|
|
105
|
+
// dispatched in a microtask, so run after them
|
|
106
|
+
if (this.maConn.readableEnded || this.maConn.status !== 'open') {
|
|
107
|
+
queueMicrotask(() => {
|
|
108
|
+
if (this.maConn.readableEnded) {
|
|
109
|
+
this.releaseInbound();
|
|
110
|
+
}
|
|
111
|
+
if (this.maConn.status !== 'open' && this.maConn.status !== 'closing') {
|
|
112
|
+
this.onTransportClosed();
|
|
113
|
+
this.releaseOutbound();
|
|
114
|
+
}
|
|
115
|
+
});
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
/**
|
|
119
|
+
* A connection can close while still holding bytes it received. Decrypt them
|
|
120
|
+
* into our own read buffer first, so closing does not discard them and the
|
|
121
|
+
* application can still read them
|
|
122
|
+
*/
|
|
123
|
+
drainMaConn() {
|
|
124
|
+
if (this.maConn.readableEnded) {
|
|
125
|
+
return;
|
|
126
|
+
}
|
|
127
|
+
try {
|
|
128
|
+
this.maConn.resume();
|
|
129
|
+
}
|
|
130
|
+
catch (err) {
|
|
131
|
+
this.log.error('could not drain the connection before closing - %e', err);
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
/**
|
|
135
|
+
* Return the inbound cipher's slot in the shared wasm memory to the pool and
|
|
136
|
+
* zero the key material it holds. If that fails the slot leaks and the key
|
|
137
|
+
* stays resident.
|
|
138
|
+
*
|
|
139
|
+
* 'end' waits for the read buffer to drain, so a connection left paused with
|
|
140
|
+
* unread bytes never emits it and keeps its slot until the process exits
|
|
141
|
+
*/
|
|
142
|
+
releaseInbound() {
|
|
143
|
+
if (this.inboundReleased) {
|
|
144
|
+
return;
|
|
145
|
+
}
|
|
146
|
+
this.inboundReleased = true;
|
|
147
|
+
try {
|
|
148
|
+
this.inboundXor.finalize();
|
|
149
|
+
}
|
|
150
|
+
catch (err) {
|
|
151
|
+
// xsalsa20 caches a view of the shared wasm memory that any other cipher
|
|
152
|
+
// growing it detaches, and finalize does not refresh it
|
|
153
|
+
this.log.error('could not release the inbound cipher, its wasm slot and key leak - %e', err);
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
releaseOutbound() {
|
|
157
|
+
if (this.outboundReleased) {
|
|
158
|
+
return;
|
|
159
|
+
}
|
|
160
|
+
this.outboundReleased = true;
|
|
161
|
+
try {
|
|
162
|
+
this.outboundXor.finalize();
|
|
163
|
+
}
|
|
164
|
+
catch (err) {
|
|
165
|
+
this.log.error('could not release the outbound cipher, its wasm slot and key leak - %e', err);
|
|
166
|
+
}
|
|
48
167
|
}
|
|
49
168
|
async sendClose(options) {
|
|
50
169
|
await this.maConn.close(options);
|
|
@@ -52,7 +171,7 @@ export class BoxMessageStream extends AbstractMultiaddrConnection {
|
|
|
52
171
|
sendData(data) {
|
|
53
172
|
return {
|
|
54
173
|
sentBytes: data.byteLength,
|
|
55
|
-
canSendMore: this.maConn.send(this.outboundXor
|
|
174
|
+
canSendMore: this.maConn.send(xorInChunks(this.outboundXor, data.subarray()))
|
|
56
175
|
};
|
|
57
176
|
}
|
|
58
177
|
sendReset(err) {
|
|
@@ -62,6 +181,11 @@ export class BoxMessageStream extends AbstractMultiaddrConnection {
|
|
|
62
181
|
this.maConn.pause();
|
|
63
182
|
}
|
|
64
183
|
sendResume() {
|
|
184
|
+
// the connection may have finished delivering everything it had, in which
|
|
185
|
+
// case resuming it throws
|
|
186
|
+
if (this.maConn.readableEnded) {
|
|
187
|
+
return;
|
|
188
|
+
}
|
|
65
189
|
this.maConn.resume();
|
|
66
190
|
}
|
|
67
191
|
}
|
package/dist/src/crypto.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"crypto.js","sourceRoot":"","sources":["../../src/crypto.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,2BAA2B,EAAE,MAAM,eAAe,CAAA;AAC3D,OAAO,EAAE,UAAU,IAAI,oBAAoB,EAAE,MAAM,yBAAyB,CAAA;AAC5E,OAAO,EAAE,QAAQ,IAAI,kBAAkB,EAAE,MAAM,uBAAuB,CAAA;AACtE,OAAO,QAAQ,MAAM,UAAU,CAAA;AAC/B,OAAO,KAAK,MAAM,MAAM,aAAa,CAAA;AACrC,OAAO,EAAE,UAAU,EAAE,MAAM,oBAAoB,CAAA;
|
|
1
|
+
{"version":3,"file":"crypto.js","sourceRoot":"","sources":["../../src/crypto.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
{
|
|
2
|
+
"ProtectorComponents": "https://libp2p.github.io/js-libp2p/interfaces/_libp2p_pnet.ProtectorComponents.html",
|
|
3
|
+
".:ProtectorComponents": "https://libp2p.github.io/js-libp2p/interfaces/_libp2p_pnet.ProtectorComponents.html",
|
|
4
|
+
"ProtectorInit": "https://libp2p.github.io/js-libp2p/interfaces/_libp2p_pnet.ProtectorInit.html",
|
|
5
|
+
".:ProtectorInit": "https://libp2p.github.io/js-libp2p/interfaces/_libp2p_pnet.ProtectorInit.html",
|
|
6
|
+
"generateKey": "https://libp2p.github.io/js-libp2p/functions/_libp2p_pnet.generateKey.html",
|
|
7
|
+
"preSharedKey": "https://libp2p.github.io/js-libp2p/functions/_libp2p_pnet.preSharedKey.html",
|
|
8
|
+
".:preSharedKey": "https://libp2p.github.io/js-libp2p/functions/_libp2p_pnet.preSharedKey.html"
|
|
9
|
+
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@libp2p/pnet",
|
|
3
|
-
"version": "3.0.
|
|
3
|
+
"version": "3.0.28",
|
|
4
4
|
"description": "Implementation of Connection protection management via a shared secret",
|
|
5
5
|
"license": "Apache-2.0 OR MIT",
|
|
6
6
|
"homepage": "https://github.com/libp2p/js-libp2p/tree/main/packages/pnet#readme",
|
|
@@ -44,8 +44,8 @@
|
|
|
44
44
|
"doc-check": "aegir doc-check"
|
|
45
45
|
},
|
|
46
46
|
"dependencies": {
|
|
47
|
-
"@libp2p/interface": "3.
|
|
48
|
-
"@libp2p/utils": "7.
|
|
47
|
+
"@libp2p/interface": "^3.3.0",
|
|
48
|
+
"@libp2p/utils": "^7.4.0",
|
|
49
49
|
"uint8arraylist": "^3.0.2",
|
|
50
50
|
"uint8arrays": "^6.1.1",
|
|
51
51
|
"xsalsa20": "^1.2.0"
|
package/src/crypto.ts
CHANGED
|
@@ -4,10 +4,30 @@ import { toString as uint8ArrayToString } from 'uint8arrays/to-string'
|
|
|
4
4
|
import xsalsa20 from 'xsalsa20'
|
|
5
5
|
import * as Errors from './errors.ts'
|
|
6
6
|
import { KEY_LENGTH } from './key-generator.ts'
|
|
7
|
-
import type { AbortOptions, MultiaddrConnection } from '@libp2p/interface'
|
|
7
|
+
import type { AbortOptions, MultiaddrConnection, StreamMessageEvent } from '@libp2p/interface'
|
|
8
8
|
import type { MessageStreamInit, SendResult } from '@libp2p/utils'
|
|
9
9
|
import type { Uint8ArrayList } from 'uint8arraylist'
|
|
10
10
|
|
|
11
|
+
const XOR_CHUNK_SIZE = 65536
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* The xsalsa20 wasm backend copies each input into linear memory, which starts
|
|
15
|
+
* at 640KiB and is capped at 62.5MiB. Growing it detaches every other cipher's
|
|
16
|
+
* view of it, so keep chunks under the initial size rather than the cap. The
|
|
17
|
+
* cipher resumes where it left off, so the result matches a single update.
|
|
18
|
+
*/
|
|
19
|
+
function xorInChunks (xor: xsalsa20.Xor, input: Uint8Array): Uint8Array {
|
|
20
|
+
const output = new Uint8Array(input.byteLength)
|
|
21
|
+
|
|
22
|
+
for (let start = 0; start < input.byteLength; start += XOR_CHUNK_SIZE) {
|
|
23
|
+
const end = Math.min(start + XOR_CHUNK_SIZE, input.byteLength)
|
|
24
|
+
|
|
25
|
+
xor.update(input.subarray(start, end), output.subarray(start, end))
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
return output
|
|
29
|
+
}
|
|
30
|
+
|
|
11
31
|
export interface BoxMessageStreamInit extends MessageStreamInit {
|
|
12
32
|
maConn: MultiaddrConnection
|
|
13
33
|
localNonce: Uint8Array
|
|
@@ -19,6 +39,9 @@ export class BoxMessageStream extends AbstractMultiaddrConnection {
|
|
|
19
39
|
private maConn: MultiaddrConnection
|
|
20
40
|
private inboundXor: xsalsa20.Xor
|
|
21
41
|
private outboundXor: xsalsa20.Xor
|
|
42
|
+
private inboundReleased: boolean
|
|
43
|
+
private outboundReleased: boolean
|
|
44
|
+
private readonly onInboundMessage: (evt: StreamMessageEvent) => void
|
|
22
45
|
|
|
23
46
|
constructor (init: BoxMessageStreamInit) {
|
|
24
47
|
super({
|
|
@@ -29,25 +52,52 @@ export class BoxMessageStream extends AbstractMultiaddrConnection {
|
|
|
29
52
|
|
|
30
53
|
this.inboundXor = xsalsa20(init.remoteNonce, init.psk)
|
|
31
54
|
this.outboundXor = xsalsa20(init.localNonce, init.psk)
|
|
55
|
+
this.inboundReleased = false
|
|
56
|
+
this.outboundReleased = false
|
|
32
57
|
this.maConn = init.maConn
|
|
33
58
|
|
|
34
|
-
this.
|
|
59
|
+
this.onInboundMessage = (evt) => {
|
|
35
60
|
const data = evt.data
|
|
36
61
|
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
62
|
+
// a peer can send data after closing and nothing upstream rejects it
|
|
63
|
+
if (this.inboundReleased) {
|
|
64
|
+
this.log('discarding %d bytes received after the readable end ended', data.byteLength)
|
|
65
|
+
return
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
try {
|
|
69
|
+
if (data instanceof Uint8Array) {
|
|
70
|
+
this.onData(xorInChunks(this.inboundXor, data))
|
|
71
|
+
} else {
|
|
72
|
+
for (const buf of data) {
|
|
73
|
+
// onData dispatches synchronously, so the consumer can abort and
|
|
74
|
+
// release the cipher part way through the message
|
|
75
|
+
if (this.inboundReleased) {
|
|
76
|
+
break
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
this.onData(xorInChunks(this.inboundXor, buf))
|
|
80
|
+
}
|
|
42
81
|
}
|
|
82
|
+
} catch (err: any) {
|
|
83
|
+
// any bytes we fail to process leave the cipher permanently out of
|
|
84
|
+
// step with the remote so tear the connection down
|
|
85
|
+
this.log.error('error decrypting inbound data - %e', err)
|
|
86
|
+
this.abort(err)
|
|
43
87
|
}
|
|
44
|
-
}
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
this.maConn.addEventListener('message', this.onInboundMessage)
|
|
45
91
|
|
|
46
92
|
// resume sending when the underlying connection can accept more data
|
|
47
93
|
this.maConn.addEventListener('drain', () => {
|
|
48
94
|
this.onMuxerDrain()
|
|
49
95
|
})
|
|
50
96
|
|
|
97
|
+
this.maConn.addEventListener('end', () => {
|
|
98
|
+
this.releaseInbound()
|
|
99
|
+
})
|
|
100
|
+
|
|
51
101
|
this.maConn.addEventListener('close', (evt) => {
|
|
52
102
|
if (evt.error != null) {
|
|
53
103
|
if (evt.local) {
|
|
@@ -55,10 +105,96 @@ export class BoxMessageStream extends AbstractMultiaddrConnection {
|
|
|
55
105
|
} else {
|
|
56
106
|
this.onRemoteReset()
|
|
57
107
|
}
|
|
108
|
+
|
|
109
|
+
// an errored connection never delivers what it still holds, so waiting
|
|
110
|
+
// for 'end' would keep the cipher forever
|
|
111
|
+
this.releaseInbound()
|
|
58
112
|
} else {
|
|
113
|
+
// the two ends have to close at different moments. the write side
|
|
114
|
+
// first, because the drain dispatches to the consumer and a reply
|
|
115
|
+
// would otherwise reach a released cipher and abort a graceful close.
|
|
116
|
+
// the read side after, because closing it while our buffer is still
|
|
117
|
+
// empty makes onData discard everything the drain produces
|
|
118
|
+
this.writeStatus = 'closed'
|
|
119
|
+
this.drainMaConn()
|
|
59
120
|
this.onTransportClosed()
|
|
60
121
|
}
|
|
122
|
+
|
|
123
|
+
// last, the branches above close the write side first and that is what
|
|
124
|
+
// stops sendData reaching a released cipher
|
|
125
|
+
this.releaseOutbound()
|
|
61
126
|
})
|
|
127
|
+
|
|
128
|
+
// the nonce handshake reads from the connection before this stream exists,
|
|
129
|
+
// so it can already have ended or closed. bytes unwrapped back onto it are
|
|
130
|
+
// dispatched in a microtask, so run after them
|
|
131
|
+
if (this.maConn.readableEnded || this.maConn.status !== 'open') {
|
|
132
|
+
queueMicrotask(() => {
|
|
133
|
+
if (this.maConn.readableEnded) {
|
|
134
|
+
this.releaseInbound()
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
if (this.maConn.status !== 'open' && this.maConn.status !== 'closing') {
|
|
138
|
+
this.onTransportClosed()
|
|
139
|
+
this.releaseOutbound()
|
|
140
|
+
}
|
|
141
|
+
})
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
/**
|
|
146
|
+
* A connection can close while still holding bytes it received. Decrypt them
|
|
147
|
+
* into our own read buffer first, so closing does not discard them and the
|
|
148
|
+
* application can still read them
|
|
149
|
+
*/
|
|
150
|
+
private drainMaConn (): void {
|
|
151
|
+
if (this.maConn.readableEnded) {
|
|
152
|
+
return
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
try {
|
|
156
|
+
this.maConn.resume()
|
|
157
|
+
} catch (err: any) {
|
|
158
|
+
this.log.error('could not drain the connection before closing - %e', err)
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
/**
|
|
163
|
+
* Return the inbound cipher's slot in the shared wasm memory to the pool and
|
|
164
|
+
* zero the key material it holds. If that fails the slot leaks and the key
|
|
165
|
+
* stays resident.
|
|
166
|
+
*
|
|
167
|
+
* 'end' waits for the read buffer to drain, so a connection left paused with
|
|
168
|
+
* unread bytes never emits it and keeps its slot until the process exits
|
|
169
|
+
*/
|
|
170
|
+
private releaseInbound (): void {
|
|
171
|
+
if (this.inboundReleased) {
|
|
172
|
+
return
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
this.inboundReleased = true
|
|
176
|
+
|
|
177
|
+
try {
|
|
178
|
+
this.inboundXor.finalize()
|
|
179
|
+
} catch (err: any) {
|
|
180
|
+
// xsalsa20 caches a view of the shared wasm memory that any other cipher
|
|
181
|
+
// growing it detaches, and finalize does not refresh it
|
|
182
|
+
this.log.error('could not release the inbound cipher, its wasm slot and key leak - %e', err)
|
|
183
|
+
}
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
private releaseOutbound (): void {
|
|
187
|
+
if (this.outboundReleased) {
|
|
188
|
+
return
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
this.outboundReleased = true
|
|
192
|
+
|
|
193
|
+
try {
|
|
194
|
+
this.outboundXor.finalize()
|
|
195
|
+
} catch (err: any) {
|
|
196
|
+
this.log.error('could not release the outbound cipher, its wasm slot and key leak - %e', err)
|
|
197
|
+
}
|
|
62
198
|
}
|
|
63
199
|
|
|
64
200
|
async sendClose (options?: AbortOptions): Promise<void> {
|
|
@@ -68,7 +204,7 @@ export class BoxMessageStream extends AbstractMultiaddrConnection {
|
|
|
68
204
|
sendData (data: Uint8ArrayList): SendResult {
|
|
69
205
|
return {
|
|
70
206
|
sentBytes: data.byteLength,
|
|
71
|
-
canSendMore: this.maConn.send(this.outboundXor
|
|
207
|
+
canSendMore: this.maConn.send(xorInChunks(this.outboundXor, data.subarray()))
|
|
72
208
|
}
|
|
73
209
|
}
|
|
74
210
|
|
|
@@ -81,6 +217,12 @@ export class BoxMessageStream extends AbstractMultiaddrConnection {
|
|
|
81
217
|
}
|
|
82
218
|
|
|
83
219
|
sendResume (): void {
|
|
220
|
+
// the connection may have finished delivering everything it had, in which
|
|
221
|
+
// case resuming it throws
|
|
222
|
+
if (this.maConn.readableEnded) {
|
|
223
|
+
return
|
|
224
|
+
}
|
|
225
|
+
|
|
84
226
|
this.maConn.resume()
|
|
85
227
|
}
|
|
86
228
|
}
|