🎩 You're Invited:Meet the Socket team at Black Hat in Las Vegas, August 3-6.RSVP
Sign In

ssb-lan

Package Overview
Dependencies
Maintainers
1
Versions
16
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

ssb-lan - npm Package Compare versions

Comparing version
1.0.0
to
1.0.1
+8
-4
package.json
{
"name": "ssb-lan",
"description": "SSB plugin for discovery of other peers in the same LAN",
"version": "1.0.0",
"version": "1.0.1",
"homepage": "https://github.com/staltz/ssb-lan",
"main": "lib/index.js",
"types": "lib/index.d.ts",
"files": [
"lib/*",
"*.js"
],
"repository": {

@@ -30,2 +34,4 @@ "type": "git",

},
"author": "Andre Staltz <contact@staltz.com> (http://staltz.com)",
"license": "MIT",
"scripts": {

@@ -35,5 +41,3 @@ "typescript": "tsc",

"test": "npm run typescript && npm run tape"
},
"author": "Andre Staltz <contact@staltz.com> (http://staltz.com)",
"license": "MIT"
}
}
import {plugin, muxrpc} from 'secret-stack-decorators';
import {Discovery, SSBConfig} from './types';
const broadcast = require('broadcast-stream');
const Ref = require('ssb-ref');
const Keys = require('ssb-keys');
const Notify = require('pull-notify');
const debug = require('debug')('ssb:lan');
const NORMAL_PORT = require('../port');
const LEGACY_PORT = 8008;
@plugin('1.0.0')
class LAN {
private readonly ssb: Record<string, any>;
private readonly notifyDiscovery: CallableFunction & Record<string, any>;
private readonly caps: Buffer;
private readonly legacyEnabled: boolean;
private legacyBroadcast?: Record<string, any>;
private normalBroadcast?: Record<string, any>;
private int?: any;
constructor(ssb: Record<string, any>, config: SSBConfig) {
this.ssb = ssb;
this.notifyDiscovery = Notify();
this.caps = Buffer.from(config.caps.shs, 'base64');
this.legacyEnabled = config.lan?.legacy !== false;
}
private readLegacy = (buf: any) => {
if (buf.loopback) return;
const address = buf.toString();
const peerKey = Ref.getKeyFromAddress(address);
if (peerKey && peerKey !== this.ssb.id) {
this.notifyDiscovery({address, verified: false} as Discovery);
}
};
private writeLegacy() {
if (!this.legacyBroadcast) return;
const address =
this.ssb.getAddress('private') ?? this.ssb.getAddress('local');
if (address) this.legacyBroadcast.write(address);
}
private readNormal = (buf: any) => {
// split buf into [ciphertext,sig]
const ciphertext = buf.slice(0, buf.length - 64);
const sig = buf.slice(buf.length - 64, buf.length);
// decrypt address
let address: string;
try {
const obj = Keys.secretUnbox(ciphertext, this.caps);
address = obj.address;
} catch (err) {
debug('failed to interpret broadcasted message: %s', buf.toString('hex'));
return;
}
// validate address
const peerKey = Ref.getKeyFromAddress(address);
if (!peerKey) {
debug(
'failed to parse address from broadcasted message: %s',
buf.toString('hex'),
);
return;
}
// avoid discovering ourselves
if (peerKey === this.ssb.id) {
return;
}
// verify signature of address
const b64sig = sig.toString('base64') + '.sig.ed25519';
const obj = {address, signature: b64sig};
const verified = Keys.verifyObj({public: peerKey}, obj);
// notify
this.notifyDiscovery({address, verified} as Discovery);
};
private writeNormal() {
if (!this.normalBroadcast) return;
const address =
this.ssb.getAddress('private') ?? this.ssb.getAddress('local');
if (address) {
// encrypt address
const ciphertext = Keys.secretBox({address}, this.caps);
// sign address
const b64sig = Keys.signObj(this.ssb.keys, {address}).signature;
const sig = Buffer.from(b64sig.replace(/\.sig\.ed25519$/, ''), 'base64');
// concatenate [ciphertext,sig]
const payload = Buffer.concat([ciphertext, sig]);
// broadcast
this.normalBroadcast.write(payload);
}
}
private writeBoth = () => {
this.writeLegacy();
this.writeNormal();
};
@muxrpc('sync')
public start = () => {
try {
this.normalBroadcast = broadcast(NORMAL_PORT);
} catch (err) {
debug('LAN broadcast turned off because: %s', err);
this.normalBroadcast = void 0;
}
try {
this.legacyBroadcast = this.legacyEnabled
? broadcast(LEGACY_PORT)
: void 0;
} catch (err) {
debug('legacy broadcast turned off because: %s', err);
this.legacyBroadcast = void 0;
}
// Read
if (this.normalBroadcast) this.normalBroadcast.on('data', this.readNormal);
if (this.legacyBroadcast) this.legacyBroadcast.on('data', this.readLegacy);
// Write now, then periodically
this.writeBoth();
this.int = setInterval(this.writeBoth, 2e3);
if (this.int.unref) this.int.unref();
};
@muxrpc('sync')
public stop = () => {
clearInterval(this.int);
if (this.normalBroadcast) {
this.normalBroadcast.close();
this.normalBroadcast = void 0;
}
if (this.legacyBroadcast) {
this.legacyBroadcast.close();
this.legacyBroadcast = void 0;
}
};
@muxrpc('source')
public discoveredPeers = () => {
return this.notifyDiscovery.listen();
};
}
export = LAN;
export type Discovery = {
verified: boolean;
address: string;
};
export type SSBConfig = {
caps: {
shs: string;
};
lan?: {
legacy?: boolean;
};
};
const pull = require('pull-stream');
const tape = require('tape');
const Keys = require('ssb-keys');
const broadcast = require('broadcast-stream');
const port = require('../port');
const Ref = require('ssb-ref');
const createSsbServer = require('ssb-server').use(require('../lib/index'));
tape('Legacy broadcasting can be turned off', t => {
t.plan(0);
const keys = Keys.generate();
const alice = createSsbServer({
temp: 'test-lan-alice',
timeout: 1000,
port: 8008,
keys,
lan: {
legacy: false,
},
});
alice.lan.start();
const b = broadcast(8008);
b.on('data', buf => {
t.fail('No UDP packet should have been received, but we did receive');
});
setTimeout(() => {
b.close();
alice.lan.stop();
alice.close();
t.end();
}, 3000);
});
tape('Legacy (when enabled) broadcasting looks correct', t => {
t.plan(1);
const keys = Keys.generate();
const alice = createSsbServer({
temp: 'test-lan-alice',
timeout: 1000,
port: 8008,
keys,
});
alice.lan.start();
const b = broadcast(8008);
b.on('data', buf => {
const msg = buf.toString();
t.true(Ref.isAddress(msg), 'broadcasted content is a multiserver address');
b.close();
alice.lan.stop();
alice.close();
setTimeout(() => {
t.end();
}, 1000);
});
});
tape('broadcast write is correct', t => {
t.plan(6);
const keys = Keys.generate();
const alice = createSsbServer({
temp: 'test-lan-alice1',
timeout: 1000,
port: 8008,
keys,
});
alice.lan.start();
const b = broadcast(port);
b.on('data', buf => {
try {
JSON.parse(buf.toString());
t.fail('JSON parsing must fail');
} catch (err) {
t.ok(err, 'JSON parsing should fail, because it is ciphertext');
}
t.true(buf.length > 64, 'UDP payload has many bytes');
const ciphertext = buf.slice(0, buf.length - 64);
t.true(ciphertext.length > 64, 'ciphertext has many bytes');
const sig = buf.slice(buf.length - 64, buf.length);
const signature = sig.toString('base64') + '.sig.ed25519';
t.equals(sig.length, 64, 'signature has 64 bytes');
const {address} = Keys.secretUnbox(ciphertext, alice.config.caps.shs);
t.true(Ref.isAddress(address), 'plaintext is a multiserver address');
const key = Ref.getKeyFromAddress(address);
const verification = Keys.verifyObj({public: key}, {address, signature});
t.true(verification, 'signature is verified');
b.close();
alice.lan.stop();
alice.close();
setTimeout(() => {
t.end();
}, 1000);
});
});
tape('broadcast read is correct', t => {
t.plan(4);
const aliceKeys = Keys.generate();
const alice = createSsbServer({
temp: 'test-lan-alice2',
timeout: 1000,
port: 8009,
keys: aliceKeys,
});
alice.lan.start();
pull(
alice.lan.discoveredPeers(),
pull.drain(discovery => {
t.equals(typeof discovery.address, 'string', 'address is a string');
t.ok(discovery.address, 'address is okay');
t.equals(
discovery.address.slice(0, 4),
'net:',
'address begins with net:',
);
t.true(discovery.verified, 'discovery is verified');
b.close();
alice.lan.stop();
alice.close();
setTimeout(() => {
t.end();
}, 1000);
}),
);
const bobKeys = Keys.generate();
const address =
'net:192.168.1.11:26830~shs:' + bobKeys.public.replace(/\.ed25519$/, '');
const ciphertext = Keys.secretBox({address}, alice.config.caps.shs);
const b64signature = Keys.signObj(bobKeys, {address}).signature;
const signature = Buffer.from(
b64signature.replace(/\.sig\.ed25519$/, ''),
'base64',
);
const payload = Buffer.concat([ciphertext, signature]);
const b = broadcast(port, true);
b.write(payload);
});
{
"compilerOptions": {
"target": "es2018",
"module": "commonjs",
"lib": ["es5", "es2015", "es2016", "es2017"],
"skipLibCheck": true,
"declaration": true,
"outDir": "./lib",
"rootDir": "./src",
"removeComments": true,
"strict": true,
"alwaysStrict": true,
"noUnusedLocals": true,
"noUnusedParameters": true,
"downlevelIteration": true,
"experimentalDecorators": true,
"noImplicitReturns": true,
"moduleResolution": "node",
"esModuleInterop": false
},
"exclude": ["node_modules", "test", "lib"]
}