git ssb

0+

farewellutopia-dev / deno-ssb-experiments



Commit fdbdeb73c5078c65616783d1b0488e0de10503dd

Accepting incoming connections

Reto Gmür committed on 7/31/2021, 2:14:51 PM
Parent: 2c674952557dae85a62fe5dfc497366f39229b1c

Files changed

BoxConnection.tschanged
RPCConnection.tschanged
ScuttlebuttPeer.tschanged
config.tschanged
run.tschanged
udpPeerDiscoverer.tschanged
BoxConnection.tsView
@@ -17,13 +17,19 @@
1717 pendingData: Uint8Array | null = null;
1818
1919 async read(p: Uint8Array): Promise<number | null> {
2020 if (!this.pendingData) {
21- this.pendingData = await this.readChunk();
21 + const chunk = await this.readChunk();
22 + if (chunk === null) {
23 + return null;
24 + }
25 + if (!this.pendingData) {
26 + this.pendingData = chunk;
27 + } else {
28 + //race condition
29 + this.pendingData = concat(this.pendingData, chunk);
30 + }
2231 }
23- if (!this.pendingData) {
24- return null;
25- }
2632 //TODO merge metods to avoid copying data
2733 if (this.pendingData.length < p.length) {
2834 p.set(this.pendingData);
2935 const result = this.pendingData.length;
@@ -59,8 +65,12 @@
5965 this.serverToClientNonce,
6066 this.serverToClientKey,
6167 );
6268 increment(this.serverToClientNonce);
69 + log.debug(() =>
70 + "Read " + decodedBody + " (" + (new TextDecoder().decode(decodedBody)) +
71 + ")",
72 + );
6373 return decodedBody;
6474 } catch (error) {
6575 if (error.message.startsWith("End of reader")) {
6676 log.info("End of reader, closing.");
@@ -70,8 +80,9 @@
7080 }
7181 }
7282
7383 async write(message: Uint8Array) {
84 + log.debug("Writing " + message);
7485 const headerNonce = new Uint8Array(this.clientToServerNonce);
7586 increment(this.clientToServerNonce);
7687 const bodyNonce = new Uint8Array(this.clientToServerNonce);
7788 increment(this.clientToServerNonce);
RPCConnection.tsView
@@ -1,8 +1,9 @@
11 import BoxConnection from "./BoxConnection.ts";
22 import {
33 bytes2NumberSigned,
44 bytes2NumberUnsigned,
5 + concat,
56 isZero,
67 log,
78 readBytes,
89 } from "./util.ts";
@@ -290,12 +291,10 @@
290291 header.set(
291292 new Uint8Array(new Uint32Array([requestNumber]).buffer).reverse(),
292293 5,
293294 );
294- //or write twice?
295- //const message = concat(headerBytes, payload);
296- //await this.write(message);
297- await this.boxConnection.write(header);
298- await this.boxConnection.write(payload);
295 + //writing in one go, to ensure correct order
296 + const message = concat(header, payload);
297 + await this.boxConnection.write(message);
299298 return requestNumber;
300299 };
301300 }
ScuttlebuttPeer.tsView
@@ -1,25 +1,27 @@
11 // deno-lint-ignore-file camelcase
22 import sodium from "https://deno.land/x/sodium@0.2.0/sumo.ts";
3-import { concat, fromBase64, path, readBytes, toBase64 } from "./util.ts";
3 +import { concat, fromBase64, log, path, readBytes, toBase64 } from "./util.ts";
44 import BoxConnection from "./BoxConnection.ts";
55 import config from "./config.ts";
6 +import { advertise } from "./udpPeerDiscoverer.ts";
67
78 await sodium.ready;
89
910 /** A peer with an identity and the abity to connect to other peers using the Secure Scuttlebutt Handshake */
10-export default class ScuttlebuttPeer {
11 +export default class ScuttlebuttPeer extends EventTarget {
1112 network_identifier = fromBase64(
1213 "1KHLiKZvAvjbY1ziZEHMXawbCEIM6qwjCDm3VYRan/s=",
1314 );
1415 keyPair = getClientKeyPair();
1516 id = "@" +
1617 toBase64(
1718 this.keyPair.publicKey,
18- );
19 + ) + ".ed25519";
1920
2021 connections: BoxConnection[] = [];
2122
23 + /** perform handshake as client */
2224 async connect(
2325 address: { protocol: string; host: string; port: number; key: string },
2426 ) {
2527 // deno-lint-ignore no-this-alias
@@ -34,17 +36,12 @@
3436 const hmac = sodium.crypto_auth(
3537 clientEphemeralKeyPair.publicKey,
3638 this.network_identifier,
3739 );
38- const clientHelloMessage = new Uint8Array(
39- hmac.length + clientEphemeralKeyPair.publicKey.length,
40- );
41- clientHelloMessage.set(hmac);
42- clientHelloMessage.set(clientEphemeralKeyPair.publicKey, hmac.length);
43- return clientHelloMessage;
40 + return concat(hmac, clientEphemeralKeyPair.publicKey);
4441 };
4542
46- const authenticate = (
43 + const authenticate = async (
4744 server_longterm_pk: Uint8Array,
4845 shared_secret_ab: Uint8Array,
4946 shared_secret_aB: Uint8Array,
5047 ) => {
@@ -73,9 +70,9 @@
7370 const nonce = new Uint8Array(24);
7471 const boxKey = sodium.crypto_hash_sha256(
7572 concat(this.network_identifier, shared_secret_ab, shared_secret_aB),
7673 );
77- conn.write(sodium.crypto_secretbox_easy(boxMsg, nonce, boxKey));
74 + await conn.write(sodium.crypto_secretbox_easy(boxMsg, nonce, boxKey));
7875 return detached_signature_A;
7976 };
8077
8178 const hello = clientHello();
@@ -105,9 +102,9 @@
105102 );
106103 const server_longterm_pk = fromBase64(
107104 address.key,
108105 );
109- const detached_signature_A = authenticate(
106 + const detached_signature_A = await authenticate(
110107 server_longterm_pk,
111108 shared_secret_ab,
112109 shared_secret_aB,
113110 );
@@ -197,10 +194,178 @@
197194 this.connections.push(connection);
198195 connection.addEventListener("close", () => {
199196 this.connections = this.connections.filter((c) => c !== connection);
200197 });
198 + this.dispatchEvent(new CustomEvent("connected", { "detail": connection }));
201199 return connection;
202200 }
201 +
202 + /** perform handshake as server */
203 + async acceptConnection(conn: Deno.Conn) {
204 + const serverEphemeralKeyPair = sodium.crypto_box_keypair("uint8array");
205 + const clientHello = await readBytes(conn, 64);
206 + const client_hmac = clientHello.subarray(0, 32);
207 + const client_ephemeral_pk = clientHello.subarray(32, 64);
208 + if (
209 + !sodium.crypto_auth_verify(
210 + client_hmac,
211 + client_ephemeral_pk,
212 + this.network_identifier,
213 + )
214 + ) {
215 + throw new Error("Verification of the client's hello failed");
216 + }
217 + const serverHello = concat(
218 + sodium.crypto_auth(
219 + serverEphemeralKeyPair.publicKey,
220 + this.network_identifier,
221 + ),
222 + serverEphemeralKeyPair.publicKey,
223 + );
224 + await conn.write(serverHello);
225 + const shared_secret_ab = sodium.crypto_scalarmult(
226 + serverEphemeralKeyPair.privateKey,
227 + client_ephemeral_pk,
228 + );
229 +
230 + const shared_secret_aB = sodium.crypto_scalarmult(
231 + sodium.crypto_sign_ed25519_sk_to_curve25519(
232 + this.keyPair.privateKey,
233 + ),
234 + client_ephemeral_pk,
235 + );
236 +
237 + const msg3 = await readBytes(conn, 112);
238 +
239 + const msg3_plaintext = sodium.crypto_secretbox_open_easy(
240 + msg3,
241 + new Uint8Array(24),
242 + sodium.crypto_hash_sha256(
243 + concat(
244 + this.network_identifier,
245 + shared_secret_ab,
246 + shared_secret_aB,
247 + ),
248 + ),
249 + );
250 +
251 + if (msg3_plaintext.length !== 96) {
252 + throw Error("Invalid message length");
253 + }
254 +
255 + const detached_signature_A = msg3_plaintext.subarray(0, 64);
256 + const client_longterm_pk = msg3_plaintext.subarray(64, 96);
257 +
258 + const verification3 = sodium.crypto_sign_verify_detached(
259 + detached_signature_A,
260 + concat(
261 + this.network_identifier,
262 + this.keyPair.publicKey,
263 + sodium.crypto_hash_sha256(shared_secret_ab),
264 + ),
265 + client_longterm_pk,
266 + );
267 + if (!verification3) {
268 + throw new Error("Verification of the client's third message failed");
269 + }
270 +
271 + const shared_secret_Ab = sodium.crypto_scalarmult(
272 + serverEphemeralKeyPair.privateKey,
273 + sodium.crypto_sign_ed25519_pk_to_curve25519(client_longterm_pk),
274 + );
275 + const detached_signature_B = sodium.crypto_sign_detached(
276 + concat(
277 + this.network_identifier,
278 + detached_signature_A,
279 + client_longterm_pk,
280 + sodium.crypto_hash_sha256(shared_secret_ab),
281 + ),
282 + this.keyPair.privateKey,
283 + );
284 + const completionMsg = sodium.crypto_secretbox_easy(
285 + detached_signature_B,
286 + new Uint8Array(24),
287 + sodium.crypto_hash_sha256(
288 + concat(
289 + this.network_identifier,
290 + shared_secret_ab,
291 + shared_secret_aB,
292 + shared_secret_Ab,
293 + ),
294 + ),
295 + );
296 + await conn.write(completionMsg);
297 +
298 + //FIXME code duplicatio
299 + const serverToClientKey = sodium.crypto_hash_sha256(
300 + concat(
301 + sodium.crypto_hash_sha256(sodium.crypto_hash_sha256(
302 + concat(
303 + this.network_identifier,
304 + shared_secret_ab,
305 + shared_secret_aB,
306 + shared_secret_Ab,
307 + ),
308 + )),
309 + client_longterm_pk,
310 + ),
311 + );
312 +
313 + const clientToServerKey = sodium.crypto_hash_sha256(
314 + concat(
315 + sodium.crypto_hash_sha256(sodium.crypto_hash_sha256(
316 + concat(
317 + this.network_identifier,
318 + shared_secret_ab,
319 + shared_secret_aB,
320 + shared_secret_Ab,
321 + ),
322 + )),
323 + this.keyPair.publicKey,
324 + ),
325 + );
326 +
327 + const serverToClientNonce = sodium.crypto_auth(
328 + client_ephemeral_pk,
329 + this.network_identifier,
330 + ).slice(0, 24);
331 + const clientToServerNonce = sodium.crypto_auth(
332 + serverEphemeralKeyPair.publicKey,
333 + this.network_identifier,
334 + ).slice(0, 24);
335 +
336 + const connection = new BoxConnection(
337 + conn,
338 + clientToServerKey,
339 + clientToServerNonce,
340 + serverToClientKey,
341 + serverToClientNonce,
342 + );
343 + this.connections.push(connection);
344 + connection.addEventListener("close", () => {
345 + this.connections = this.connections.filter((c) => c !== connection);
346 + });
347 + this.dispatchEvent(new CustomEvent("connected", { "detail": connection }));
348 + }
349 +
350 + async listen() {
351 + const listener = Deno.listen({
352 + port: config.port,
353 + });
354 + log.info(`listening on port ${config.port}`);
355 + (async () => {
356 + for await (const conn of listener) {
357 + log.info(`Received connection from ${conn.remoteAddr}`);
358 + await this.acceptConnection(conn);
359 + //conn.close();
360 + }
361 + })();
362 + await advertise(
363 + `net:${(listener.addr as Deno.NetAddr).hostname}:${
364 + (listener.addr as Deno.NetAddr).port
365 + }:~shs:${toBase64(this.keyPair.publicKey)}`,
366 + );
367 + }
203368 }
204369
205370 function getClientKeyPair() {
206371 const secretFileDir = config.baseDir;
config.tsView
@@ -17,7 +17,8 @@
1717
1818 const config = {
1919 baseDir,
2020 dataDir,
21 + port: 8008,
2122 };
2223
2324 export default config;
run.tsView
@@ -6,8 +6,10 @@
66 import RPCConnection from "./RPCConnection.ts";
77
88 const host = new ScuttlebuttPeer();
99
10 +host.listen();
11 +
1012 if (Deno.args.length !== 1) {
1113 log.error("expecting one argument");
1214 Deno.exit(1);
1315 }
@@ -16,11 +18,14 @@
1618 const address = parseAddress(
1719 addressString,
1820 );
1921
20-const boxConnection: BoxConnection = await host.connect(
22 +host.addEventListener("connected", async (options) => {
23 + log.debug("new connection");
24 + const boxConnection: BoxConnection = (options as CustomEvent).detail;
25 + const rpcConnection = new RPCConnection(boxConnection, new Procedures());
26 + await updateFeeds(rpcConnection);
27 +});
28 +
29 +await host.connect(
2130 address,
2231 );
23-
24-const rpcConnection = new RPCConnection(boxConnection, new Procedures());
25-
26-updateFeeds(rpcConnection);
udpPeerDiscoverer.tsView
@@ -1,14 +1,25 @@
1 +//import { delay } from "https://deno.land/std@0.103.0/async/mod.ts";
2 +import { log } from "./util.ts";
3 +
14 const l = Deno.listenDatagram({
25 port: 8008,
36 hostname: "0.0.0.0",
47 transport: "udp",
58 });
6-/*log.info(
9 +
10 +export function advertise(_multiAddress: string) {
11 + //as UDP broadcast doesn't seem to be supported
12 + //Another issue is finding out own ip
13 + //TODO implement
14 + return new Promise((_res, _rej) => {});
15 +}
16 +
17 +log.info(
718 `Listening on ${(l.addr as Deno.NetAddr).hostname}:${
819 (l.addr as Deno.NetAddr).port
920 }.`,
10-);*/
21 +);
1122
1223 const udpPeerDiscoverer = {
1324 async *[Symbol.asyncIterator]() {
1425 for await (const r of l) {

Built with git-ssb-web