diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..8793fab --- /dev/null +++ b/.gitignore @@ -0,0 +1,2 @@ +*.pem +node_modules diff --git a/README.md b/README.md new file mode 100644 index 0000000..aaab7cc --- /dev/null +++ b/README.md @@ -0,0 +1,27 @@ +# ZeroNet JS +A JS version of ZeroNet + +What works: + Well, nothing at the moment + +WIP: + - The protocol + +ToDo: + - Everything else + +# Running +Just run `client.js` and it should connect to localhost:15411 and get the content.json of HelloZeroNet (that's all it does for now) + +# Certificate +That will be useful later +``` +openssl req -x509 -newkey rsa:4096 -keyout key.pem -out cert.pem -days 365 -nodes -config cert.conf << . + + + + + + +. +``` diff --git a/cert.conf b/cert.conf new file mode 100644 index 0000000..f238b23 --- /dev/null +++ b/cert.conf @@ -0,0 +1,80 @@ +[ req ] +default_bits = 2048 +default_keyfile = server-key.pem +distinguished_name = subject +req_extensions = req_ext +x509_extensions = x509_ext +string_mask = utf8only + +# The Subject DN can be formed using X501 or RFC 4514 (see RFC 4519 for a description). +# Its sort of a mashup. For example, RFC 4514 does not provide emailAddress. +[ subject ] +countryName = Country Name (2 letter code) +countryName_default = US + +stateOrProvinceName = State or Province Name (full name) +stateOrProvinceName_default = NY + +localityName = Locality Name (eg, city) +localityName_default = New York + +organizationName = Organization Name (eg, company) +organizationName_default = Example, LLC + +# Use a friendly name here because its presented to the user. The server's DNS +# names are placed in Subject Alternate Names. Plus, DNS names here is deprecated +# by both IETF and CA/Browser Forums. If you place a DNS name here, then you +# must include the DNS name in the SAN too (otherwise, Chrome and others that +# strictly follow the CA/Browser Baseline Requirements will fail). +commonName = Common Name (e.g. server FQDN or YOUR name) +commonName_default = Example Company + +emailAddress = Email Address +emailAddress_default = test@example.com + +# Section x509_ext is used when generating a self-signed certificate. I.e., openssl req -x509 ... +[ x509_ext ] + +subjectKeyIdentifier = hash +authorityKeyIdentifier = keyid,issuer + +# You only need digitalSignature below. *If* you don't allow +# RSA Key transport (i.e., you use ephemeral cipher suites), then +# omit keyEncipherment because that's key transport. +basicConstraints = CA:FALSE +keyUsage = digitalSignature, keyEncipherment +subjectAltName = @alternate_names +nsComment = "OpenSSL Generated Certificate" + +# RFC 5280, Section 4.2.1.12 makes EKU optional +# CA/Browser Baseline Requirements, Appendix (B)(3)(G) makes me confused +# In either case, you probably only need serverAuth. +# extendedKeyUsage = serverAuth, clientAuth + +# Section req_ext is used when generating a certificate signing request. I.e., openssl req ... +[ req_ext ] + +subjectKeyIdentifier = hash + +basicConstraints = CA:FALSE +keyUsage = digitalSignature, keyEncipherment +subjectAltName = @alternate_names +nsComment = "OpenSSL Generated Certificate" + +# RFC 5280, Section 4.2.1.12 makes EKU optional +# CA/Browser Baseline Requirements, Appendix (B)(3)(G) makes me confused +# In either case, you probably only need serverAuth. +# extendedKeyUsage = serverAuth, clientAuth + +[ alternate_names ] + +DNS.1 = localhost + +# Add these if you need them. But usually you don't want them or +# need them in production. You may need them for development. +# DNS.5 = localhost +# DNS.6 = localhost.localdomain +# DNS.7 = 127.0.0.1 + +# IPv6 localhost +# DNS.8 = ::1 diff --git a/client.js b/client.js new file mode 100644 index 0000000..58a11fe --- /dev/null +++ b/client.js @@ -0,0 +1,14 @@ +const net = require('net') + +const msgpack = require("msgpack") + +const Protocol = require("./lib/protocol/") + +const sock = net.connect({ + host: "localhost", + port: 15441 +}, err => { + if (err) console.error(err) + const client = new Protocol(sock) + client.getFile("1HeLLo4uzjaLetFx6NH3PMwFP3qbRbTf3D", "content.json", 100, console.log) +}) diff --git a/index.js b/index.js new file mode 100644 index 0000000..0aec2d9 --- /dev/null +++ b/index.js @@ -0,0 +1,23 @@ +const tls = require('tls') +const net = require('net') + +const msgpack = require("msgpack") + +//new tls.TLSSocket() +const sock = net.connect({ + host: "localhost", + port: 15441 +}, err => { + if (err) console.error(err) + const s = new msgpack.Stream(sock) + s.on("msg", console.log) + sock.write(msgpack.pack({ + "cmd": "getFile", + "req_id": 1, + "params": { + "site": "1HeLLo4uzjaLetFx6NH3PMwFP3qbRbTf3D", + "inner_path": "content.json", + "location": 0 + } + })) +}) diff --git a/lib/protocol/index.js b/lib/protocol/index.js new file mode 100644 index 0000000..3558955 --- /dev/null +++ b/lib/protocol/index.js @@ -0,0 +1,130 @@ +"use strict" + +const msgpack = require("msgpack") + +module.exports = function Protocol(stream) { + //Turns a stream into a zeronet protocol client + const msg = new msgpack.Stream(stream) + const self = this + let req_id = 0 //req_id + let cbs = {} + + const buffer_size = 1024 * 512 //aka 512kb + + function write(data) { + stream.write(msgpack.pack(data)) + } + + function request(cmd, params, cb) { + req_id++ + cbs[req_id] = cb + write({ + cmd, + params, + req_id + }) + } + + msg.on("msg", data => { + if (data.to) { + if (cbs[data.to]) { + cbs[data.to](data) + delete cbs[data.to] + } + } else if (data.cmd) { + console.log("got %s", data.cmd, data) + msg.emit(data.cmd, data) + } + }) + + function validate(def, param) { + //validate if "param" matches "def" + for (var p in def) { + switch (typeof def[p]) { + case "function": + if (!def[p](param[p])) throw new Error("Invalid value for key " + p + " (validation function)") + break; + case "string": + if (typeof param[p] != def[p]) throw new Error("Invalid value for key " + p + " (typeof)") + break; + } + } + } + + function createProtocolFunction(name, params, ret) { + //name=the command + //params=the parameters with which to be called (object key => type/value/validation function) + //return=the parameters that we get as a response (object key => type/value/validation function) + const fnc = function PeerRequest() { + const a = [].slice.call(arguments, 0) //turn "arguments" into array + let i = 0 + const reqData = {} //req data as object + Object.keys(params).forEach(key => { + reqData[key] = a[i] + i++ + }) + + const cb = a[i] //final callback + if (typeof cb != "function") throw new Error("CB must be a function") + + try { + validate(params, reqData) + request(name, reqData, data => { + if (data.error) { + let e = new Error(data.error) + e.raw = data.error + e.stack = "Error: " + data.error + "\n at PeerReqest(" + name + ")" + "\n at ZeroNet Protocol" + return cb(e) + } else { + const respData = {} + const cba = [null] //array as argument for cb, first is err + Object.keys(ret).forEach(key => { + respData[key] = data[key] + cba.push(data[key]) + }) + validate(ret, respData) + return cb.apply(data, cba) //and call the callback + } + }) + } catch (e) { + return cb(e) + } + } + self[name] = fnc + } + + //Peer requests (see: https://zeronet.readthedocs.io/en/latest/help_zeronet/network_protocol/) + + createProtocolFunction("getFile", { + site: "string", + inner_path: "string", + location: "number" + }, { + body: Buffer, + location: "number", + size: "number" + }) + + createProtocolFunction("ping", {}, { + body: b => b == "pong" + }) + + createProtocolFunction("pex", { + site: "string", + peers: "string", + need: "number" + }, { + peers: "string" + }) + + createProtocolFunction("update", { + site: "string", + inner_path: "string", + body: "object" + }, { + ok: "string" + }) + + //self.registerHandler=(handler) => msg.on("") + +} diff --git a/package-lock.json b/package-lock.json new file mode 100644 index 0000000..dfab64c --- /dev/null +++ b/package-lock.json @@ -0,0 +1,17 @@ +{ + "name": "zeronet", + "version": "0.0.1", + "lockfileVersion": 1, + "dependencies": { + "msgpack": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/msgpack/-/msgpack-1.0.2.tgz", + "integrity": "sha1-kj4sXP+mXIQY6bIo0RJHk5acQpw=" + }, + "nan": { + "version": "2.6.2", + "resolved": "https://registry.npmjs.org/nan/-/nan-2.6.2.tgz", + "integrity": "sha1-5P805slf37WuzAjeZZb0NgWn20U=" + } + } +} diff --git a/package.json b/package.json new file mode 100644 index 0000000..b7f00bf --- /dev/null +++ b/package.json @@ -0,0 +1,23 @@ +{ + "name": "zeronet", + "version": "0.0.1", + "description": "ZeroNet JS", + "main": "index.js", + "directories": { + "lib": "lib" + }, + "dependencies": { + "msgpack": "^1.0.2" + }, + "devDependencies": {}, + "scripts": { + "test": "echo \"Error: no test specified\" && exit 1" + }, + "keywords": [ + "zeronet", + "js", + "p2p" + ], + "author": "Maciej Krüger ", + "license": "GPL-3.0" +}