├── src ├── http │ ├── api │ │ ├── resources │ │ │ ├── root.js │ │ │ ├── version.js │ │ │ ├── index.js │ │ │ ├── key.js │ │ │ ├── db.js │ │ │ ├── orbitdns.js │ │ │ ├── domain.js │ │ │ ├── repo.js │ │ │ └── config.js │ │ └── routes │ │ │ ├── import.js │ │ │ ├── version.js │ │ │ ├── index.js │ │ │ ├── orbitdns.js │ │ │ ├── config.js │ │ │ ├── key.js │ │ │ ├── db.js │ │ │ ├── domain.js │ │ │ └── repo.js │ ├── error-handler.js │ └── index.js ├── core │ ├── index.js │ ├── types │ │ ├── index.js │ │ ├── SignedObject.js │ │ ├── DataTypes.js │ │ └── RecordTypes.js │ ├── components │ │ ├── index.js │ │ ├── version.js │ │ ├── db.js │ │ ├── net.js │ │ ├── config.js │ │ ├── root.js │ │ ├── key.js │ │ └── domain.js │ ├── resolver.js │ ├── repo.js │ ├── core.js │ └── AccessController.js └── cli │ ├── commands │ ├── config.js │ ├── net.js │ ├── root.js │ ├── key.js │ ├── domain.js │ ├── domain │ │ ├── register.js │ │ └── list.js │ ├── help.js │ ├── init.js │ ├── commands.js │ └── daemon.js │ ├── parser.js │ ├── cli.js │ ├── daemon.js │ └── utils.js ├── .gitignore ├── Makefile ├── Dockerfile ├── package.json ├── LICENSE └── README.md /src/http/api/resources/root.js: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/core/index.js: -------------------------------------------------------------------------------- 1 | module.exports = require('./core') -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | orbitdb 3 | *-lock.json 4 | .credentials.json -------------------------------------------------------------------------------- /src/http/api/routes/import.js: -------------------------------------------------------------------------------- 1 | const resources = require('../resources') 2 | -------------------------------------------------------------------------------- /src/core/types/index.js: -------------------------------------------------------------------------------- 1 | exports.DataTypes = require('./DataTypes') 2 | exports.RecordTypes = require('./RecordTypes') 3 | exports.SignedObject = require('./SignedObject') -------------------------------------------------------------------------------- /src/http/api/routes/version.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | const resources = require('../resources') 4 | 5 | module.exports = { 6 | method: '*', 7 | path: '/api/v0/version', 8 | handler: resources.version 9 | } -------------------------------------------------------------------------------- /src/core/components/index.js: -------------------------------------------------------------------------------- 1 | exports.domain = require('./domain'); 2 | exports.key = require('./key'); 3 | exports.config = require('./config'); 4 | exports.root = require('./root'); 5 | exports.db = require('./db') 6 | exports.version = require('./version') -------------------------------------------------------------------------------- /src/http/api/resources/version.js: -------------------------------------------------------------------------------- 1 | 2 | module.exports = async (request, h) => { 3 | const { orbitdns } = request.server.app 4 | const version = await orbitdns.version() 5 | 6 | return h.response({ 7 | Version: version.version, 8 | }) 9 | } 10 | -------------------------------------------------------------------------------- /src/cli/commands/config.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | module.exports = { 4 | command: 'config ', 5 | 6 | description: 'Manage configuration', 7 | 8 | builder (yargs) { 9 | return yargs.commandDir('config'); 10 | }, 11 | 12 | handler (argv) { 13 | } 14 | } -------------------------------------------------------------------------------- /src/cli/commands/net.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | module.exports = { 4 | command: 'net ', 5 | 6 | description: 'Interact wth network management', 7 | 8 | builder (yargs) { 9 | return yargs.commandDir('net'); 10 | }, 11 | 12 | handler (argv) { 13 | } 14 | } -------------------------------------------------------------------------------- /src/cli/commands/root.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | module.exports = { 4 | command: 'root ', 5 | 6 | description: 'Interact with root TLD management', 7 | 8 | builder (yargs) { 9 | return yargs.commandDir('root'); 10 | }, 11 | 12 | handler (argv) { 13 | } 14 | } -------------------------------------------------------------------------------- /src/cli/commands/key.js: -------------------------------------------------------------------------------- 1 | const Yargs = require('yargs') 2 | 3 | module.exports = { 4 | command: "key ", 5 | describe: "Key management.", 6 | 7 | builder(yargs) { 8 | return yargs.commandDir('key'); 9 | }, 10 | 11 | handler(argv) { 12 | } 13 | } -------------------------------------------------------------------------------- /src/cli/commands/domain.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | module.exports = { 4 | command: 'domain ', 5 | 6 | description: 'Interact with domain name management.', 7 | 8 | builder (yargs) { 9 | return yargs.commandDir('domain'); 10 | }, 11 | 12 | handler (argv) { 13 | } 14 | } -------------------------------------------------------------------------------- /src/http/api/resources/index.js: -------------------------------------------------------------------------------- 1 | exports.orbitdns = require('./orbitdns') 2 | exports.version = require('./version') 3 | exports.domain = require('./domain') 4 | exports.key = require('./key') 5 | exports.config = require('./config') 6 | exports.repo = require('./repo') 7 | exports.db = require("./db") -------------------------------------------------------------------------------- /src/http/api/routes/index.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | module.exports = [ 4 | ...require('./orbitdns'), 5 | require('./version'), 6 | ...require('./domain'), 7 | ...require('./key'), 8 | ...require('./config'), 9 | ...require('./repo'), 10 | ...require('./db') 11 | ] -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | build: 2 | docker build -t orbitdns . 3 | 4 | daemon: 5 | docker run \ 6 | -v `pwd`/.orbitdns:/root/.orbitdns \ 7 | --name=orbitdns \ 8 | orbitdns daemon 9 | 10 | 11 | clean: 12 | docker rm -f orbitdns || true 13 | docker rmi -f orbitdns:latest || true 14 | 15 | 16 | rebuild: clean build 17 | -------------------------------------------------------------------------------- /src/core/components/version.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | const pkg = require('../../../package.json') 4 | 5 | module.exports = function version(self) { 6 | return () => { 7 | return new Promise((resolve, reject) => { 8 | resolve({ 9 | version: pkg.version 10 | }); 11 | }); 12 | } 13 | } -------------------------------------------------------------------------------- /src/cli/commands/domain/register.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | command: 'register ', 3 | 4 | description: 'Creates a new domain name.', 5 | 6 | builder: { 7 | key: { 8 | alias: "k", 9 | describe: "" 10 | } 11 | }, 12 | 13 | handler(argv) { 14 | console.log(argv) 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /src/http/api/routes/orbitdns.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | const resources = require('../resources') 4 | 5 | module.exports = [ 6 | { 7 | method: "*", 8 | path: "/test", 9 | handler: resources.orbitdns.test.handler 10 | }, { 11 | method: "*", 12 | path: "/api/v0/resolve", 13 | handler: resources.orbitdns.resolve.handler 14 | } 15 | ]; -------------------------------------------------------------------------------- /src/cli/commands/domain/list.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | command: 'list ', 3 | 4 | description: 'List available domains.', 5 | 6 | builder: { 7 | 8 | }, 9 | 10 | handler(argv) { 11 | argv.resolve((async () => { 12 | //console.log(argv) 13 | const orbitdns = await argv.getOrbitDNS() 14 | console.log(orbitdns) 15 | })()) 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /src/cli/commands/help.js: -------------------------------------------------------------------------------- 1 | const Yargs = require('yargs') 2 | 3 | module.exports = { 4 | command: "help", 5 | describe: "Prints help command.", 6 | /** 7 | * 8 | * @param {Yargs} yargs 9 | */ 10 | builder(yargs) { 11 | return yargs; //No options as of now. 12 | }, 13 | handler(argv) { 14 | argv.resolve((async () => { 15 | })()); 16 | } 17 | } -------------------------------------------------------------------------------- /src/core/components/db.js: -------------------------------------------------------------------------------- 1 | const OrbitDB = require('orbit-db') 2 | const Core = require('../core') 3 | /** 4 | * 5 | * @param {Core} self 6 | */ 7 | async function db(self) { 8 | var db = await self.repo.orbitdb.docstore(self.config.get("netID"), { 9 | indexBy: "id", accessController: { 10 | type: 'orbitdns', 11 | write: ["*"] 12 | } 13 | }) 14 | await db.load(); 15 | return db; 16 | } 17 | module.exports = db -------------------------------------------------------------------------------- /Dockerfile: -------------------------------------------------------------------------------- 1 | FROM node:13.3.0-alpine3.10 as builder 2 | WORKDIR /home/node/app 3 | 4 | ENV NODE_ENV=production 5 | 6 | # First stage: install dependencies 7 | RUN apk add --no-cache git python3 make g++ 8 | COPY ./package*.json ./ 9 | RUN npm install 10 | 11 | # Second stage, run app 12 | FROM node:13.3.0-alpine3.10 13 | WORKDIR /home/node/app 14 | 15 | COPY --from=builder /home/node/app/node_modules ./node_modules 16 | COPY --from=builder /home/node/app/package*.json ./ 17 | 18 | COPY src/ /home/node/app/src/ 19 | 20 | EXPOSE 80 21 | EXPOSE 53 22 | 23 | ENTRYPOINT ["node", "src/cli/cli.js"] 24 | -------------------------------------------------------------------------------- /src/http/api/routes/config.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | const resources = require('../resources') 4 | const Joi = require('@hapi/joi') 5 | 6 | module.exports = [ 7 | { 8 | method: '*', 9 | path: '/api/v0/config/{key?}', 10 | options: { 11 | pre: [ 12 | { method: resources.config.getOrSet.parseArgs, assign: 'args' } 13 | ] 14 | }, 15 | handler: resources.config.getOrSet.handler 16 | }, 17 | { 18 | method: '*', 19 | path: '/api/v0/config/show', 20 | handler: resources.config.show 21 | }, 22 | ] -------------------------------------------------------------------------------- /src/core/components/net.js: -------------------------------------------------------------------------------- 1 | const Core = require('../core') 2 | class net { 3 | /** 4 | * 5 | * @param {Core} self 6 | */ 7 | constructor(self) { 8 | this.self = self; 9 | } 10 | import(cert) { 11 | 12 | } 13 | /** 14 | * @return {String} 15 | */ 16 | async create() { 17 | var db = await this.self.repo.orbitdb.docstore("orbitdns", { 18 | indexBy: 'id', 19 | accessController: { 20 | write: ["*"] 21 | } 22 | }) 23 | return db.address; 24 | } 25 | } 26 | module.exports = net; -------------------------------------------------------------------------------- /src/cli/parser.js: -------------------------------------------------------------------------------- 1 | const yargs = require('yargs') 2 | const utils = require('./utils') 3 | 4 | const parser = yargs 5 | .commandDir('commands') 6 | .fail((msg, err, yargs) => { 7 | if (err) { 8 | throw err // preserve stack 9 | } 10 | utils.print(msg); 11 | yargs.showHelp(); 12 | }) 13 | .epilog(utils.ipfsPathHelp) 14 | .middleware(argv => Object.assign(argv, { 15 | getOrbitDNS: utils.singleton(cb => utils.getOrbitDNS(argv, cb)), 16 | print: utils.print, 17 | isDaemonOn: utils.isDaemonOn, 18 | getRepoPath: utils.getRepoPath 19 | })) 20 | .demandCommand(1) 21 | .help() 22 | .strict() 23 | .completion() 24 | 25 | 26 | module.exports = parser -------------------------------------------------------------------------------- /src/http/api/routes/key.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | const resources = require('../resources') 4 | const Joi = require('@hapi/joi') 5 | 6 | module.exports = [ 7 | { 8 | method: "*", 9 | path: "/api/v0/key/seed", 10 | handler: resources.key.seed.handler 11 | }, 12 | { 13 | method: "*", 14 | path: "/api/v0/key/gen", 15 | options: { 16 | validate: resources.orbitdns.resolve.validate 17 | }, 18 | handler: resources.key.gen.handler 19 | }, 20 | { 21 | method: "*", 22 | path: "/api/v0/key/list", 23 | handler: resources.key.list.handler 24 | }, 25 | { 26 | method: "*", 27 | path: "/api/v0/key/get", 28 | handler: resources.key.get.handler 29 | } 30 | ] -------------------------------------------------------------------------------- /src/http/api/routes/db.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | const resources = require('../resources') 4 | const Joi = require('@hapi/joi') 5 | 6 | module.exports = [ 7 | { 8 | method: "*", 9 | path: `/api/v0/db/get/{key*}`, 10 | options: { 11 | handler: resources.db.get.handler 12 | } 13 | }, 14 | { 15 | method: "*", 16 | path: `/api/v0/db/put/`, 17 | options: { 18 | handler: resources.db.put.handler, 19 | } 20 | }, 21 | { 22 | method: "*", 23 | path: `/api/v0/db/del/{key*}`, 24 | options: { 25 | handler: resources.db.del.handler, 26 | validate: { 27 | params: { 28 | key: Joi.string().required() 29 | } 30 | } 31 | } 32 | } 33 | ] -------------------------------------------------------------------------------- /src/cli/commands/init.js: -------------------------------------------------------------------------------- 1 | const Yargs = require('yargs') 2 | 3 | module.exports = { 4 | command: "init", 5 | describe: "Create a new OrbitDNS data folder.", 6 | /** 7 | * 8 | * @param {Yargs} yargs 9 | */ 10 | builder(yargs) { 11 | return yargs; //No options as of now. 12 | }, 13 | handler(argv) { 14 | console.log("Initializing new OrbitDNS data folder.") 15 | argv.resolve((async () => { 16 | const Core = require("../../core/"); 17 | const repoPath = argv.getRepoPath() 18 | var daemon = new Core({ 19 | directory: repoPath 20 | }); 21 | try { 22 | await daemon.init(); 23 | 24 | } catch (err) { 25 | console.log(err) 26 | } 27 | 28 | })()); 29 | 30 | } 31 | } -------------------------------------------------------------------------------- /src/cli/commands/commands.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | const path = require('path') 4 | const glob = require('glob').sync 5 | 6 | module.exports = { 7 | command: 'commands', 8 | 9 | describe: 'List all available commands', 10 | 11 | handler ({ print }) { 12 | const basePath = path.resolve(__dirname, '..') 13 | 14 | // modeled after https://github.com/vdemedes/ronin/blob/master/lib/program.js#L78 15 | const files = glob(path.join(basePath, 'commands', '**', '*.js')) 16 | const cmds = files.map((p) => { 17 | return p.replace(/\//g, path.sep) 18 | .replace(/^./, ($1) => $1.toUpperCase()) 19 | .replace(path.join(basePath, 'commands'), '') 20 | .replace(path.sep, '') 21 | .split(path.sep) 22 | .join(' ') 23 | .replace('.js', '') 24 | }).sort().map((cmd) => `orbitdns ${cmd}`) 25 | console.log(files) 26 | //console.log(['orbitdns'].concat(cmds).join('\n')) 27 | print(['orbitdns'].concat(cmds).join('\n')) 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /src/http/api/resources/key.js: -------------------------------------------------------------------------------- 1 | const Core = require("../../../core/core"); 2 | 3 | exports.gen = { 4 | async handler(request, h) { 5 | const { orbitdns } = request.server.app; 6 | const { name, type, seed } = request.payload; 7 | orbitdns.key.gen(name, type, seed); 8 | return h.response(); 9 | } 10 | } 11 | exports.seed = { 12 | async handler(request, h) { 13 | const { orbitdns } = request.server.app; 14 | return h.response(orbitdns.key.seed()); 15 | } 16 | } 17 | exports.list = { 18 | async handler(request, h) { 19 | const { orbitdns } = request.server.app; 20 | 21 | return h.response(await orbitdns.key.list()); 22 | } 23 | } 24 | exports.get = { 25 | async handler(request, h) { 26 | /** 27 | * @type {Core} 28 | */ 29 | const orbitdns = request.server.app.orbitdns; 30 | const { name } = request.payload; 31 | return h.response(await orbitdns.key.get(name)); 32 | } 33 | } -------------------------------------------------------------------------------- /src/http/api/resources/db.js: -------------------------------------------------------------------------------- 1 | const DagCbor = require('ipld-dag-cbor') 2 | const base64url = require('base64url') 3 | 4 | exports.get = { 5 | async handler(request, h) { 6 | const orbitdns = request.server.app.orbitdns; 7 | if(!request.orig.params) { 8 | return h.response(base64url.encode(DagCbor.util.serialize(orbitdns.db.get("")))); 9 | } 10 | const key = request.orig.params.key; 11 | return h.response(base64url.encode(DagCbor.util.serialize(orbitdns.db.get(key)))); 12 | } 13 | } 14 | exports.put = { 15 | async handler(request, h) { 16 | const {orbitdns} = request.server.app; 17 | const value = request.payload.value; 18 | return h.response(await orbitdns.db.put(DagCbor.util.deserialize(base64url.toBuffer(value)))); 19 | } 20 | } 21 | exports.del = { 22 | async handler(request, h) { 23 | const orbitdns = request.server.app.orbitdns; 24 | const key = request.orig.params.key; 25 | return h.response(await orbitdns.db.del(key)); 26 | } 27 | } -------------------------------------------------------------------------------- /src/cli/cli.js: -------------------------------------------------------------------------------- 1 | const YargsPromise = require('yargs-promise') 2 | const parser = require('./parser') 3 | const cli = new YargsPromise(parser) 4 | //const commandAlias = require('./command-alias') 5 | const { print } = require('./utils') 6 | 7 | 8 | const args = process.argv.slice(2) 9 | cli 10 | .parse(args) 11 | .then((input) => { 12 | const { data, argv } = input; 13 | //getIpfs = argv.getIpfs 14 | if (data) { 15 | print(data) 16 | } 17 | }) 18 | .catch((bug) => { 19 | //getIpfs = argv.getIpfs 20 | console.log(bug) 21 | /*if (error.message) { 22 | print(error.message) 23 | debug(error) 24 | } else { 25 | print('Unknown error, please re-run the command with DEBUG=ipfs:cli to see debug output') 26 | debug(error) 27 | }*/ 28 | process.exit(1) 29 | }) 30 | .finally(() => { 31 | //if (getIpfs && getIpfs.instance) { 32 | //const cleanup = getIpfs.rest[0] 33 | //return cleanup() 34 | //} 35 | }) 36 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "orbitdns", 3 | "version": "1.0.0", 4 | "description": "Decentralized, P2P, cryptographically signed DNS.", 5 | "main": "index.js", 6 | "scripts": { 7 | "start": "node src/cli/cli.js", 8 | "test": "echo \"Error: no test specified\" && exit 1" 9 | }, 10 | "bin": { 11 | "orbitdns": "src/cli/cli.js" 12 | }, 13 | "repository": { 14 | "type": "git", 15 | "url": "git+https://github.com/vaultec81/orbitdns.git" 16 | }, 17 | "license": "MIT", 18 | "bugs": { 19 | "url": "https://github.com/vaultec81/orbitdns/issues" 20 | }, 21 | "homepage": "https://github.com/vaultec81/orbitdns#readme", 22 | "dependencies": { 23 | "@hapi/hapi": "^18.4.0", 24 | "base64url": "^3.0.1", 25 | "bip39": "^3.0.2", 26 | "buffer-json": "^2.0.0", 27 | "datastore-fs": "^0.9.1", 28 | "elliptic": "^6.5.1", 29 | "ipfs": "^0.40.0", 30 | "ipld-dag-cbor": "^0.15.0", 31 | "node-ipc": "^9.1.1", 32 | "node-named": "0.0.1", 33 | "orbit-db": "^0.23.0", 34 | "p-map-series": "^2.1.0", 35 | "orbitdns-http-client": "^1.0.0" 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2019 vaultec, localmesh.org 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /src/http/api/routes/domain.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | const resources = require('../resources') 4 | const Joi = require('@hapi/joi') 5 | 6 | module.exports = [ 7 | { 8 | method: "*", 9 | path: "/api/v0/domain/get/{domain*}", 10 | options: { 11 | handler: resources.domain.get.handler, 12 | validate: { 13 | params: { 14 | domain: Joi.string().required() 15 | } 16 | }, 17 | pre: [ 18 | //{ method: resources.domain.get.parseArgs, assign: 'args' } 19 | ] 20 | } 21 | }, 22 | { 23 | method: "*", 24 | path: "/api/v0/domain/list", 25 | handler: resources.domain.list.handler 26 | }, 27 | { 28 | method: "*", 29 | path: "/api/v0/domain/listrecords/{domain*}", 30 | options: { 31 | handler: resources.domain.listRecords.handler, 32 | validate: { 33 | params: { 34 | domain: Joi.string().required() 35 | } 36 | }, 37 | pre: [ 38 | //{ method: resources.domain.get.parseArgs, assign: 'args' } 39 | ] 40 | } 41 | } 42 | ] -------------------------------------------------------------------------------- /src/http/error-handler.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | module.exports = server => { 4 | server.ext('onPreResponse', (request, h) => { 5 | const res = request.response 6 | 7 | if (!res.isBoom) { 8 | return h.continue 9 | } 10 | 11 | const message = res.message || res.output.payload.message 12 | const { statusCode } = res.output.payload 13 | let code 14 | 15 | if (res.data && res.data.code != null) { 16 | code = res.data.code 17 | } else { 18 | // Map status code to error code as defined by go-ipfs 19 | // https://github.com/ipfs/go-ipfs-cmdkit/blob/0262a120012063c359727423ec703b9649eec447/error.go#L12-L20 20 | if (statusCode >= 400 && statusCode < 500) { 21 | code = statusCode === 404 ? 3 : 1 22 | } else { 23 | code = 0 24 | } 25 | } 26 | 27 | if (process.env.DEBUG || statusCode >= 500) { 28 | const { req } = request.raw 29 | const debug = { 30 | method: req.method, 31 | url: request.url.path, 32 | headers: req.headers, 33 | info: request.info, 34 | payload: request.payload, 35 | response: res.output.payload 36 | } 37 | 38 | server.logger().error(debug) 39 | server.logger().error(res) 40 | } 41 | 42 | return h.response({ 43 | Message: message, 44 | Code: code, 45 | Type: 'error' 46 | }).code(statusCode) 47 | }) 48 | } 49 | -------------------------------------------------------------------------------- /src/cli/commands/daemon.js: -------------------------------------------------------------------------------- 1 | const Yargs = require('yargs') 2 | const fs = require('fs'); 3 | 4 | module.exports = { 5 | command: "daemon", 6 | describe: "Start a long running daemon process", 7 | /** 8 | * 9 | * @param {Yargs} yargs 10 | */ 11 | builder(yargs) { 12 | return yargs; //No options as of now. 13 | }, 14 | handler(argv) { 15 | argv.resolve((async () => { 16 | const {print} = argv; 17 | const Daemon = require("../daemon"); 18 | const repoPath = argv.getRepoPath() 19 | var daemon = new Daemon({ 20 | directory: repoPath 21 | }); 22 | try { 23 | await daemon.start(); 24 | daemon._httpApi._apiServers.forEach(apiServer => { 25 | print(`API listening on ${apiServer.info.ma.toString()}`) 26 | 27 | }) 28 | } catch (err) { 29 | console.log(err) 30 | } 31 | const cleanup = async () => { 32 | //print(`Received interrupt signal, shutting down...`) 33 | await daemon.stop() 34 | process.exit(0) 35 | } 36 | 37 | // listen for graceful termination 38 | process.on('SIGTERM', cleanup) 39 | process.on('SIGINT', cleanup) 40 | process.on('SIGHUP', cleanup) 41 | })()); 42 | 43 | } 44 | } -------------------------------------------------------------------------------- /src/http/api/resources/orbitdns.js: -------------------------------------------------------------------------------- 1 | const Joi = require('@hapi/joi') 2 | const OrbitName = require('../../../core/types/DataTypes').OrbitName; 3 | 4 | exports.test = { 5 | async handler(request, h) { 6 | //const key = request.pre.args.key 7 | console.log(request.server.app.orbitdns.db.get("")) 8 | return h.response(await request.server.app.orbitdns.version()).header('X-Stream-Output', '1'); 9 | } 10 | } 11 | exports.resolve = { 12 | validate: { 13 | //domain: Joi.string(), 14 | //type: Joi.string(), 15 | //format: Joi.string() 16 | }, 17 | async handler(request, h) { 18 | /** 19 | * @type {Orbitnds} 20 | */ 21 | const {orbitdns} = request.server.app; 22 | const {domain, type, format} = request.payload; 23 | var name = OrbitName.fromDnsName(domain) 24 | console.log(type) 25 | if(type) { 26 | name.setType(type); 27 | } else { 28 | name.setType("A"); 29 | } 30 | console.log(name) 31 | if(format) { 32 | if(format === "targets") { 33 | var val = await orbitdns.resolve(name, {type: type, format: format}) 34 | console.log(val) 35 | return h.response(); 36 | 37 | } else { 38 | //Default JSON output 39 | return h.response(await orbitdns.resolve(name, {type: type, format: format})); 40 | } 41 | } else { 42 | //Default JSON output 43 | return h.response(await orbitdns.resolve(name)); 44 | } 45 | } 46 | } -------------------------------------------------------------------------------- /src/http/api/resources/domain.js: -------------------------------------------------------------------------------- 1 | const Joi = require('@hapi/joi') 2 | const Boom = require('@hapi/boom') 3 | const OrbitName = require('../../../core/types').DataTypes.OrbitName; 4 | 5 | // common pre request handler that parses the args and returns `key` which is assigned to `request.pre.args` 6 | exports.parseKey = (request, h) => { 7 | if (!request.query.arg) { 8 | throw Boom.badRequest("Argument 'key' is required") 9 | } 10 | 11 | } 12 | 13 | exports.get = { 14 | parseArgs: exports.parseKey, 15 | async handler(request, h) { 16 | const { orbitdns } = request.server.app; 17 | const domain = request.orig.params.domain.split("/"); 18 | const basename = domain[0]; const type = domain[1]; 19 | 20 | var name = OrbitName.fromDnsName(basename); 21 | if (type) { 22 | name.setType(type); 23 | } 24 | let dataEncoding = request.query['data-encoding']; 25 | if (dataEncoding === "text") { 26 | dataEncoding = "utf8"; 27 | } 28 | let result; 29 | console.log(name); 30 | try { 31 | result = await orbitdns.domain.get(name); 32 | } catch (err) { 33 | throw Boom.badRequest(err); 34 | } 35 | console.log(result); 36 | if (!result) { 37 | return h.response({}); 38 | } 39 | return h.response(result); 40 | } 41 | } 42 | exports.list = { 43 | async handler(request, h) { 44 | const { orbitdns } = request.server.app; 45 | } 46 | } 47 | exports.listRecords = { 48 | async handler(request, h) { 49 | const { orbitdns } = request.server.app; 50 | const domain = request.orig.params.domain; 51 | var res = await orbitdns.domain.listRecords(domain); 52 | return h.response(res) 53 | } 54 | } 55 | exports.setRecord = { 56 | async handler(request, h) { 57 | 58 | } 59 | } -------------------------------------------------------------------------------- /src/cli/daemon.js: -------------------------------------------------------------------------------- 1 | const OrbitDNS = require('../core') 2 | const HttpApi = require('../http') 3 | const Resolver = require('../core/resolver') 4 | const fs = require('fs') 5 | const path = require('path') 6 | 7 | class Daemon { 8 | constructor(options) { 9 | this._options = options || {}; 10 | } 11 | async start() { 12 | const orbitdns = new OrbitDNS(); 13 | orbitdns.start(); 14 | await new Promise((resolve, reject) => { 15 | orbitdns.once('error', err => { 16 | //this._log('error starting core', err) 17 | err.code = 'ENOENT'; 18 | reject(err) 19 | }) 20 | orbitdns.once('start', () => { 21 | resolve() 22 | }) 23 | }) 24 | this._orbitdns = orbitdns; 25 | const httpApi = new HttpApi(this._orbitdns, {}) 26 | this._httpApi = await httpApi.start() 27 | if (this._httpApi._apiServers.length) { 28 | //await promisify(ipfs._repo.apiAddr.set)(this._httpApi._apiServers[0].info.ma) 29 | } 30 | this._httpApi._apiServers.forEach(apiServer => { 31 | var str = apiServer.info.ma.toString(); 32 | if(str.includes("0.0.0.0")) { 33 | str = str.replace("0.0.0.0", "127.0.0.1") 34 | } 35 | console.log(str) 36 | //fs.writeFileSync(path.join(this._options.directory, "apiaddr"), apiServer.info.ma.toString()) 37 | }) 38 | console.log(Resolver) 39 | this.resolver = new Resolver(this._orbitdns); 40 | this.resolver.start(); 41 | return this; 42 | } 43 | async stop() { 44 | await Promise.all([ 45 | //this._httpApi && this._httpApi.stop(), 46 | this._orbitdns && this._orbitdns.stop(), 47 | 48 | 49 | ]) 50 | 51 | return this; 52 | } 53 | } 54 | module.exports = Daemon; -------------------------------------------------------------------------------- /src/core/resolver.js: -------------------------------------------------------------------------------- 1 | const Named = require('node-named') 2 | const dns = require('dns'); 3 | const OrbitDNS = require('.') 4 | 5 | var records = {}; 6 | [ 7 | 'A', 8 | 'AAAA', 9 | 'MX', 10 | 'SOA', 11 | 'SRV', 12 | 'TXT', 13 | 'AAAA', 14 | 'CNAME' 15 | ].forEach(function (r) { 16 | var lcr = r.toLowerCase(); 17 | records[lcr.toUpperCase()] = require("node-named/lib/" + 'records/' + lcr); 18 | }); 19 | class Resolver { 20 | /** 21 | * 22 | * @param {OrbitDNS} orbitdns 23 | * @param {Object} options 24 | */ 25 | constructor(orbitdns, options) { 26 | this.orbitdns = orbitdns; 27 | //this._options = options || {}; 28 | this.named = Named.createServer(); 29 | if(!options) { 30 | options = {host: "::ffff:127.0.0.1", port: 53}; 31 | } 32 | const {address} = options; 33 | 34 | this.port = options.port; this.host = options.host; 35 | } 36 | async query(name, type) { 37 | return this.orbitdns.domain.get(name, {type:type}) 38 | } 39 | start() { 40 | this.named.listen(this.port, this.host, ()=> { 41 | console.log('DNS server started on port '+this.port); 42 | }); 43 | this.named.on('query', async (query) => { 44 | var type = query.type(); 45 | var domain = query.name(); 46 | console.log('DNS Query: %s', domain); 47 | //var target = new named.SOARecord(domain, { serial: 12345 }); 48 | console.log(records); 49 | //var target = new records[query.type()]("127.0.0.1"); 50 | //query.addAnswer(domain, target, 300); 51 | 52 | if(type === "SOA") { 53 | var r = await this.query(domain, type) 54 | console.log(r) 55 | if(r.length !== 0) { 56 | for(var value of r) { 57 | var record = value.record; 58 | var target = new records[type](domain,{ 59 | ttl: record.ttl, 60 | expire: record.expire 61 | }) 62 | } 63 | query.addAnswer(domain, target, 300) 64 | } 65 | } 66 | 67 | 68 | this.named.send(query); 69 | }) 70 | } 71 | } 72 | module.exports = Resolver; -------------------------------------------------------------------------------- /src/http/api/routes/repo.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | const resources = require('../resources') 4 | const Joi = require('@hapi/joi') 5 | 6 | class datastoreapi { 7 | constructor(name) { 8 | this.list = [ 9 | { 10 | method: "*", 11 | path: `/api/v0/repo/${name}/get/{key*}`, 12 | options: { 13 | handler: resources.repo[name].get.handler, 14 | validate: { 15 | params: { 16 | key: Joi.string().required() 17 | } 18 | }, 19 | pre: [ 20 | //{ method: resources.domain.get.parseArgs, assign: 'args' } 21 | ] 22 | } 23 | }, 24 | { 25 | method: "*", 26 | path: `/api/v0/repo/${name}/put/{key*}`, 27 | options: { 28 | handler: resources.repo[name].put.handler, 29 | validate: { 30 | params: { 31 | key: Joi.string().required() 32 | } 33 | } 34 | } 35 | }, 36 | { 37 | method: "*", 38 | path: `/api/v0/repo/${name}/has/{key*}`, 39 | options: { 40 | handler: resources.repo[name].has.handler, 41 | validate: { 42 | params: { 43 | key: Joi.string().required() 44 | } 45 | } 46 | } 47 | }, 48 | { 49 | method: "*", 50 | path: `/api/v0/repo/${name}/delete/{key*}`, 51 | options: { 52 | handler: resources.repo[name].delete.handler, 53 | validate: { 54 | params: { 55 | key: Joi.string().required() 56 | } 57 | } 58 | } 59 | }, 60 | { 61 | method: "*", 62 | path: `/api/v0/repo/${name}/query/{query*}`, 63 | options: { 64 | encoding: null, 65 | handler: resources.repo[name].query.handler, 66 | validate: { 67 | params: { 68 | query: Joi.string().required() 69 | } 70 | } 71 | } 72 | } 73 | ] 74 | } 75 | } 76 | module.exports = [ 77 | ...new datastoreapi("datastore").list, 78 | ...new datastoreapi("keystore").list, 79 | ...new datastoreapi("root").list 80 | ] -------------------------------------------------------------------------------- /src/core/repo.js: -------------------------------------------------------------------------------- 1 | const datastoreFs = require('datastore-fs'); 2 | const fs = require('fs'); 3 | const IPFS = require('ipfs'); 4 | const OrbitDB = require('orbit-db') 5 | const path = require('path'); 6 | 7 | /** 8 | * OrbitDNS repo 9 | * Currently supports nodejs native only. 10 | */ 11 | class Repo { 12 | /** 13 | * @param {String} repoPath - path where the repo is stored 14 | * @param {Object} opts - options pased down from parent 15 | */ 16 | constructor(repoPath, opts) { 17 | this.closed = true 18 | this.path = repoPath 19 | opts = opts || {}; 20 | 21 | this.keystore = null; //DNSKeystore 22 | this.ipfs = opts.ipfs || null; 23 | this.orbitdb = opts.orbitdb || null; 24 | } 25 | async init() { 26 | if (!fs.existsSync(this.path)) { 27 | fs.mkdirSync(this.path); 28 | } 29 | } 30 | _isInitialized() { 31 | if (!fs.existsSync(this.path)) { 32 | return true; 33 | } 34 | return false; 35 | } 36 | async open() { 37 | if (!this.keystore) 38 | this.keystore = new datastoreFs(path.join(this.path, "keystore")); 39 | 40 | if (!this.ipfs) { 41 | /** 42 | * @type {IPFS} 43 | */ 44 | this.ipfs = new IPFS({ 45 | config: { 46 | Addresses: { 47 | Swarm: [ 48 | "/ip4/0.0.0.0/tcp/4005", 49 | "/ip4/0.0.0.0/tcp/4006/ws" 50 | ] 51 | } 52 | }, 53 | start: true, 54 | EXPERIMENTAL: { 55 | pubsub: true 56 | }, 57 | repo: path.join(this.path, "ipfs") 58 | }) 59 | //await this.ipfs.init(); 60 | 61 | await this.ipfs.ready; 62 | } 63 | if(!this.orbitdb) { 64 | let AccessControllers = require('orbit-db-access-controllers') 65 | AccessControllers.addAccessController({ AccessController: require('./AccessController') }) 66 | this.orbitdb = await OrbitDB.createInstance(this.ipfs, { 67 | AccessControllers: AccessControllers, 68 | directory: path.join(this.path, "orbitdb") 69 | }); 70 | } 71 | //TLD root certs 72 | if(!this.root) { 73 | this.root = new datastoreFs(path.join(this.path, "root"), { 74 | extension: "trct" 75 | }); 76 | } 77 | this.datastore = new datastoreFs(path.join(this.path, "datastore")) 78 | this.closed = false; 79 | } 80 | async close() { 81 | await this.orbitdb.disconnect() 82 | this.keystore.close() 83 | this.ipfs.stop() 84 | this.datastore.close() 85 | this.closed = true; 86 | } 87 | } 88 | module.exports = Repo; -------------------------------------------------------------------------------- /src/core/components/config.js: -------------------------------------------------------------------------------- 1 | const Core = require('../core') 2 | const path = require('path'); 3 | const fs = require('fs'); 4 | const _get = require('dlv') 5 | const mergeOptions = require('merge-options') 6 | 7 | function obj_set(obj, props, value) { 8 | if (typeof props == 'string') { 9 | props = props.split('.'); 10 | } 11 | if (typeof props == 'symbol') { 12 | props = [props]; 13 | } 14 | var lastProp = props.pop(); 15 | if (!lastProp) { 16 | return false; 17 | } 18 | var thisProp; 19 | while ((thisProp = props.shift())) { 20 | if (typeof obj[thisProp] == 'undefined') { 21 | obj[thisProp] = {}; 22 | } 23 | obj = obj[thisProp]; 24 | if (!obj || typeof obj != 'object') { 25 | return false; 26 | } 27 | } 28 | obj[lastProp] = value; 29 | return true; 30 | } 31 | 32 | class config { 33 | /** 34 | * 35 | * @param {Core} self 36 | * @param {String} dir 37 | */ 38 | constructor(self, dir) { 39 | this.self = self; 40 | this.path = path.join(dir, "config"); 41 | } 42 | /** 43 | * Reloads config from fs. 44 | */ 45 | reload() { 46 | var buf = fs.readFileSync(this.path).toString(); 47 | var obj = JSON.parse(buf); 48 | //patch 49 | this.config = mergeOptions(this.confg, obj); 50 | } 51 | save() { 52 | var buf = Buffer.from(JSON.stringify(this.config, null, 2)); 53 | fs.writeFileSync(this.path, buf); 54 | } 55 | /** 56 | * 57 | * @param {String} key 58 | */ 59 | get(key) { 60 | if (typeof key === 'undefined') { 61 | return this.config; 62 | } 63 | 64 | if (typeof key !== 'string') { 65 | return new Error('Key ' + key + ' must be a string.'); 66 | } 67 | return _get(this.config, key); 68 | } 69 | /** 70 | * 71 | * @param {String} key 72 | * @param {*} value 73 | */ 74 | set(key, value) { 75 | obj_set(this.config, key, value); 76 | this.save(); 77 | } 78 | /** 79 | * Load config from json. 80 | */ 81 | async open() { 82 | if(!fs.existsSync(this.path)) { 83 | this.init(); 84 | return; 85 | } 86 | var buf = fs.readFileSync(this.path); 87 | this.config = JSON.parse(buf); 88 | } 89 | /** 90 | * Creates config with default settings 91 | * @param {Object} config custom config object 92 | */ 93 | async init(config) { 94 | const defaultConfig = { 95 | netID: "/orbitdb/zdpuAnNJE7XuhdpS9rRBUzTooifzyGWE84zwM4futwaRH9Fgd/orbitdns", 96 | Addresses: { 97 | API: "/ip4/127.0.0.1/tcp/6001" 98 | }, 99 | resolver: { 100 | upstream: "1.1.1.1" 101 | } 102 | 103 | }; 104 | this.config = config || defaultConfig; 105 | this.save(); 106 | } 107 | _isInitialized() { 108 | if(!fs.existsSync(this.path)) { 109 | return false; 110 | } 111 | return true; 112 | } 113 | } 114 | module.exports = config; -------------------------------------------------------------------------------- /src/http/api/resources/repo.js: -------------------------------------------------------------------------------- 1 | const Joi = require('@hapi/joi'); 2 | const Boom = require('@hapi/boom'); 3 | const Core = require('../../../core'); 4 | const Key = require('interface-datastore').Key; 5 | const DagCbor = require('ipld-dag-cbor'); 6 | const base64url = require('base64url'); 7 | 8 | class keyvalueapi { 9 | constructor(keystorename) { 10 | var name = keystorename; 11 | this.get = { 12 | async handler (request, h) { 13 | /** 14 | * @type {Core} 15 | */ 16 | const orbitdns = request.server.app.orbitdns; 17 | const store = orbitdns.repo[name]; 18 | const key = request.orig.params.key; 19 | if(!(await store.has(new Key(key)))) { 20 | throw Boom.notFound(); 21 | } 22 | 23 | return h.response(base64url.encode(await store.get(new Key(key)))).type("text/plain"); 24 | } 25 | }; 26 | this.put = { 27 | async handler (request, h) { 28 | /** 29 | * @type {Core} 30 | */ 31 | const orbitdns = request.server.app.orbitdns; 32 | const store = orbitdns.repo[name]; 33 | const key = request.orig.params.key; 34 | const value = request.payload.value; 35 | if(!value) { 36 | throw Boom.badRequest("value param required") 37 | } 38 | 39 | return h.response(await store.put(new Key(key), base64url.toBuffer(value))) 40 | } 41 | }; 42 | this.has = { 43 | async handler (request, h) { 44 | /** 45 | * @type {Core} 46 | */ 47 | const orbitdns = request.server.app.orbitdns; 48 | const store = orbitdns.repo[name]; 49 | const key = request.orig.params.key; 50 | return h.response(await store.has(new Key(key))); 51 | } 52 | }; 53 | this.delete = { 54 | async handler (request, h) { 55 | /** 56 | * @type {Core} 57 | */ 58 | const orbitdns = request.server.app.orbitdns; 59 | const store = orbitdns.repo[name]; 60 | const key = request.orig.params.key; 61 | return h.response(await store.delete(new Key(key))); 62 | } 63 | }; 64 | this.query = { 65 | async handler (request, h) { 66 | /** 67 | * @type {Core} 68 | */ 69 | const orbitdns = request.server.app.orbitdns; 70 | const store = orbitdns.repo[name]; 71 | const query = DagCbor.util.deserialize(base64url.toBuffer(request.orig.params.query)); 72 | var it = store.query(query); 73 | let out = []; 74 | for await (let val of it) { 75 | out.push(val) 76 | } 77 | return h.response(base64url.encode(DagCbor.util.serialize(out))).type("application/base64"); 78 | } 79 | }; 80 | } 81 | } 82 | exports.keystore = new keyvalueapi("keystore"); 83 | exports.datastore = new keyvalueapi("datastore"); 84 | exports.root = new keyvalueapi("root") -------------------------------------------------------------------------------- /src/cli/utils.js: -------------------------------------------------------------------------------- 1 | const fs = require('fs') 2 | const os = require('os') 3 | const multiaddr = require('multiaddr') 4 | const path = require('path') 5 | const promisify = require('promisify-es6') 6 | const debug = require('debug') 7 | const log = debug('cli') 8 | log.error = debug('cli:error') 9 | const Core = require("../core/core") 10 | 11 | exports.getRepoPath = () => { 12 | return process.env.ORBITDNS_PATH || os.homedir() + '/.orbitdns' 13 | } 14 | 15 | exports.isDaemonOn = isDaemonOn 16 | function isDaemonOn() { 17 | console.log(path.join(exports.getRepoPath(), 'apiaddr')) 18 | try { 19 | fs.readFileSync(path.join(exports.getRepoPath(), 'apiaddr')) 20 | log('daemon is on') 21 | return true 22 | } catch (err) { 23 | log('daemon is off') 24 | return false 25 | } 26 | } 27 | exports.getAPICtl = getAPICtl 28 | function getAPICtl(apiAddr) { 29 | if (!apiAddr && !isDaemonOn()) { 30 | throw new Error('daemon is not on') 31 | } 32 | if (!apiAddr) { 33 | const apiPath = path.join(exports.getRepoPath(), 'apiaddr') 34 | apiAddr = multiaddr(fs.readFileSync(apiPath).toString()).toString() 35 | } 36 | // Required inline to reduce startup time 37 | const APIctl = require('orbitdns-http-client') 38 | return new APIctl(apiAddr) 39 | } 40 | exports.getOrbitDNS = async (argv, callback) => { 41 | console.log(isDaemonOn()) 42 | console.log(argv.api) 43 | if (argv.api || isDaemonOn()) { 44 | return callback(null, getAPICtl(argv.api), promisify((cb) => cb())) 45 | } 46 | // Required inline to reduce startup time 47 | const Core = require('../core/') 48 | const node = new Core({ 49 | directory: exports.getRepoPath() 50 | }) 51 | 52 | const cleanup = async () => { 53 | if (node) { 54 | node.close() 55 | } 56 | } 57 | 58 | 59 | node.on('error', (err) => { 60 | throw err 61 | }) 62 | 63 | node.once('ready', () => { 64 | callback(null, node, cleanup) 65 | }) 66 | } 67 | exports.singleton = create => { 68 | const requests = [] 69 | const getter = promisify(cb => { 70 | if (getter.instance) return cb(null, getter.instance, ...getter.rest) 71 | requests.push(cb) 72 | if (requests.length > 1) return 73 | create((err, instance, ...rest) => { 74 | getter.instance = instance 75 | getter.rest = rest 76 | while (requests.length) requests.pop()(err, instance, ...rest) 77 | }) 78 | }) 79 | return getter 80 | } 81 | 82 | 83 | let visible = true 84 | exports.disablePrinting = () => { visible = false } 85 | 86 | exports.print = (msg, newline, isError = false) => { 87 | if (newline === undefined) { 88 | newline = true 89 | } 90 | 91 | if (visible) { 92 | if (msg === undefined) { 93 | msg = '' 94 | } 95 | msg = newline ? msg + '\n' : msg 96 | const outStream = isError ? process.stderr : process.stdout 97 | outStream.write(msg) 98 | } 99 | } 100 | 101 | exports.ipfsPathHelp = 'orbitdns uses a repository in the local file system. By default, the repo is ' + 102 | 'located at ~/.orbitdns. To change the repo location, set the $ORBITDNS_PATH environment variable:\n\n' + 103 | 'export ORBITDNS_PATH=/path/to/ipfsrepo\n' -------------------------------------------------------------------------------- /src/core/types/SignedObject.js: -------------------------------------------------------------------------------- 1 | const Utils = require('elliptic').utils; 2 | const EC = require('elliptic').ec; 3 | const dagCBOR = require('ipld-dag-cbor') 4 | 5 | class SignedObject { 6 | /** 7 | * 8 | * @param {KeyPair} pair 9 | * @param {String} type EC curve type 10 | * @returns {SignedObject} 11 | */ 12 | sign(pair, type) { 13 | var obj = JSON.parse(JSON.stringify(this)); //Simple object copy! 14 | delete obj["$signature"]; delete obj["$publickey"]; delete obj["$type"] 15 | var derSign = pair.sign(Utils.toArray(dagCBOR.util.serialize(obj))).toDER() 16 | obj["$signature"] = Buffer.from(derSign).toString('base64'); 17 | obj["$publickey"] = pair.getPublic(true, 'hex'); 18 | obj["$type"] = type; 19 | var signedObject = new SignedObject(); 20 | 21 | for (var prop in obj) 22 | signedObject[prop] = obj[prop]; 23 | return signedObject; 24 | } 25 | /** 26 | * Checks whether object is valid 27 | * @param {*} publicKey 28 | * @returns {Boolean} 29 | */ 30 | validate(publicKey) { 31 | var obj = JSON.parse(JSON.stringify(this)); //Simple object copy! 32 | if(!obj["$signature"]) { 33 | return false; 34 | } 35 | var sig = Buffer.from(obj["$signature"], 'base64'); 36 | var type = obj["$type"]; 37 | delete obj["$signature"]; delete obj["$publickey"]; delete obj["$type"]; //Remove data not important to signature 38 | 39 | var ec = new EC(type) 40 | let pub; 41 | if (publicKey === Buffer) { 42 | pub = ec.keyFromPublic(Utils.toArray(publicKey), 'bin') 43 | //ec.keyFromPublic 44 | } else if (typeof publicKey === "string") { 45 | pub = ec.keyFromPublic(publicKey, 'hex') //Assume using hex 46 | } 47 | return pub.verify(Utils.toArray(dagCBOR.util.serialize(obj)), Utils.toArray(sig)) 48 | } 49 | /** 50 | * Converts Object into CborDag 51 | * @returns {Buffer} 52 | */ 53 | toCborDag() { 54 | return dagCBOR.util.serialize(this) 55 | } 56 | static is(obj) { 57 | if(obj["$signature"] && obj["$publickey"] && obj["$type"]) { 58 | return true 59 | } else { 60 | return false; 61 | } 62 | } 63 | /** 64 | * 65 | * @param {Object} obj 66 | * @returns {SignedObject} 67 | */ 68 | static cast(obj) { 69 | var signedObject = new SignedObject(); 70 | 71 | for (var prop in obj) 72 | signedObject[prop] = obj[prop]; 73 | return signedObject; 74 | } 75 | /** 76 | * 77 | * @param {JSON} json 78 | * @returns {SignedObject} 79 | */ 80 | static fromJSON(json) { 81 | let obj; 82 | if (typeof json === "string") { 83 | obj = JSON.parse(json); 84 | } else if (typeof json === "object") { 85 | obj = json; 86 | } 87 | var signedObject = new SignedObject(); 88 | 89 | for (var prop in obj) 90 | signedObject[prop] = obj[prop]; 91 | return signedObject; 92 | } 93 | /** 94 | * Converts CborDag into javascript object 95 | * @param {Buffer} bin 96 | */ 97 | static fromCborDag(bin) { 98 | return SignedObject.cast(dagCBOR.util.deserialize(bin)) 99 | } 100 | } 101 | 102 | exports = module.exports = SignedObject; -------------------------------------------------------------------------------- /src/http/api/resources/config.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | const Joi = require('@hapi/joi') 4 | const get = require('dlv') 5 | const set = require('just-safe-set') 6 | const Boom = require('@hapi/boom') 7 | 8 | exports.getOrSet = { 9 | // pre request handler that parses the args and returns `key` & `value` which are assigned to `request.pre.args` 10 | parseArgs(request, h) { 11 | const parseValue = (args) => { 12 | if (request.query.bool !== undefined) { 13 | args.value = args.value === 'true' 14 | } else if (request.query.json !== undefined) { 15 | try { 16 | args.value = JSON.parse(args.value) 17 | } catch (err) { 18 | log.error(err) 19 | throw Boom.badRequest('failed to unmarshal json. ' + err) 20 | } 21 | } 22 | 23 | return args 24 | } 25 | 26 | if (request.query.arg instanceof Array) { 27 | return parseValue({ 28 | key: request.query.key, 29 | value: request.query.arg 30 | }) 31 | } 32 | 33 | if (request.params.key) { 34 | return parseValue({ 35 | key: request.params.key, 36 | value: request.query.value 37 | }) 38 | } 39 | 40 | if (!request.query.arg) { 41 | throw Boom.badRequest("Argument 'key' is required") 42 | } 43 | 44 | return { key: request.query.arg } 45 | }, 46 | 47 | // main route handler which is called after the above `parseArgs`, but only if the args were valid 48 | async handler(request, h) { 49 | const { orbitdns } = request.server.app 50 | const { key } = request.pre.key 51 | let { value } = request.pre.value 52 | 53 | // check that value exists - typeof null === 'object' 54 | if (value && (typeof value === 'object' && 55 | value.type === 'Buffer')) { 56 | throw Boom.badRequest('Invalid value type') 57 | } 58 | 59 | let originalConfig 60 | try { 61 | originalConfig = await orbitdns.config.get() 62 | } catch (err) { 63 | throw Boom.boomify(err, { message: 'Failed to get config value' }) 64 | } 65 | 66 | if (value === undefined) { 67 | // Get the value of a given key 68 | value = get(originalConfig, key) 69 | if (value === undefined) { 70 | throw Boom.notFound('Failed to get config value: key has no attributes') 71 | } 72 | } else { 73 | // Set the new value of a given key 74 | const result = set(originalConfig, key, value) 75 | if (!result) { 76 | throw Boom.badRequest('Failed to set config value') 77 | } 78 | try { 79 | await orbitdns.config.replace(originalConfig) 80 | } catch (err) { 81 | throw Boom.boomify(err, { message: 'Failed to replace config value' }) 82 | } 83 | } 84 | 85 | return h.response({ 86 | key: key, 87 | value: value 88 | }) 89 | } 90 | } 91 | 92 | exports.show = async (request, h) => { 93 | const { orbitdns } = request.server.app 94 | 95 | let config 96 | try { 97 | config = await orbitdns.config.get() 98 | } catch (err) { 99 | throw Boom.boomify(err, { message: 'Failed to get config value' }) 100 | } 101 | 102 | return h.response(config) 103 | } 104 | -------------------------------------------------------------------------------- /src/core/components/root.js: -------------------------------------------------------------------------------- 1 | const Core = require('../core') 2 | const Key = require('interface-datastore').Key; 3 | const DNSKey = require('../types/DataTypes').DNSKey; 4 | const CborDag = require('ipld-dag-cbor') 5 | const OrbitName = require('../types/').DataTypes.OrbitName 6 | 7 | /** 8 | * Management for root TLD DNSKeys 9 | */ 10 | class root { 11 | /** 12 | * 13 | * @param {Core} self 14 | */ 15 | constructor(self) { 16 | this.self = self; 17 | this.rootStore = this.self.repo.root; 18 | } 19 | /** 20 | * 21 | * @param {String} tld 22 | * @param {DNSKey} signingKey 23 | * @param {DNSKey[]} DNSKeys 24 | */ 25 | async create(tld, signingKey, DNSKeys) { 26 | signingKey = DataTypes.DNSKey.cast(signingKey) 27 | var orbitname = DataTypes.OrbitName.fromDnsName(tldName) 28 | var inDNSKeys = []; 29 | if (DNSKeys) { 30 | for (var key of DNSKeys) { 31 | inDNSKeys.push(key.clean()); 32 | } 33 | } else { 34 | throw "DNSKeys required"; 35 | } 36 | if(!signingKey) { 37 | throw "signingKey required" 38 | } 39 | orbitname.setType("SOA"); 40 | var tld = SignedObject.cast(RecordTypes.SOA.create(tldName, DNSKeys, 0, 0, 240000)) 41 | tld.id = orbitname.toOrbitName() 42 | return await this.db.put(tld.sign(signingKey.getPair(), signingKey.type)) 43 | } 44 | /** 45 | * 46 | * @param {String} name 47 | * @param {*} cert 48 | */ 49 | import(name, cert) { 50 | 51 | } 52 | /** 53 | * 54 | * @param {String} name 55 | * @param {String} format 56 | */ 57 | export(name, format) { 58 | this.rootStore.get(new Key(name)) 59 | } 60 | /** 61 | * 62 | * @param {String} name 63 | */ 64 | remove(name) { 65 | 66 | } 67 | /** 68 | * 69 | * @param {String} name 70 | * @param {DNSKey[]} DNSKeys 71 | */ 72 | create(name, DNSKeys) { 73 | 74 | 75 | var obj = {}; 76 | obj.name = name; 77 | 78 | } 79 | async get(tld) { 80 | console.log(await this.rootStore.get(new Key(tld))); 81 | } 82 | } 83 | 84 | class rootCert { 85 | /** 86 | * 87 | * @param {root} mgr 88 | */ 89 | constructor(mgr) { 90 | /** 91 | * @type {root} 92 | */ 93 | this.mgr = mgr; 94 | } 95 | /** 96 | * Creates a default structure for rootCert 97 | */ 98 | template() { 99 | this.cert = { 100 | signingKeys: [], 101 | /** 102 | * @type {String} 103 | */ 104 | domain: null 105 | }; 106 | //Signature template. 107 | } 108 | serialize() { 109 | CborDag.util.serialize(this.cert); 110 | } 111 | async save() { 112 | if(mgr) { 113 | await mgr.self.rootStore.put(new Key(this.cert.domain), this.serialize()) 114 | } else { 115 | throw "function not avaliable" 116 | } 117 | } 118 | static deserialize(bin) { 119 | var obj = {cert: CborDag.util.deserialize(bin)} 120 | return rootCert.cast(obj) 121 | } 122 | /** 123 | * 124 | * @param {Object} obj 125 | * @returns {rootCert} 126 | */ 127 | static cast(obj) { 128 | var cert = new rootCert(); 129 | 130 | for (var prop in obj) 131 | cert[prop] = obj[prop]; 132 | return cert; 133 | } 134 | } 135 | 136 | module.exports = root; 137 | module.exports.rootCert = rootCert; -------------------------------------------------------------------------------- /src/core/components/key.js: -------------------------------------------------------------------------------- 1 | const Core = require('../core') 2 | const bip39 = require('bip39') 3 | const EC = require('elliptic').ec; 4 | const Key = require('interface-datastore').Key; 5 | const CborDag = require('ipld-dag-cbor') 6 | const DNSKey = require('../types/DataTypes').DNSKey; 7 | 8 | /** 9 | * Class to manage keystore 10 | */ 11 | class key { 12 | /** 13 | * 14 | * @param {Core} self 15 | */ 16 | constructor(self) { 17 | this.self = self; 18 | } 19 | /** 20 | * Generate 21 | * @param {String} name 22 | * @param {String} type ed25519 secp256k1 p256 23 | * @param {String} seed 24 | * @returns {Promise} 25 | */ 26 | async gen(name, type, seed) { 27 | console.log(name) 28 | if (!type) { 29 | type = "ed25519"; //Default 30 | } 31 | var ec = new EC(type); 32 | let entropy; 33 | let pair; 34 | if (seed) { 35 | entropy = bip39.mnemonicToSeedSync(seed); 36 | pair = ec.genKeyPair({ entropy: entropy }); 37 | } else { 38 | pair = ec.genKeyPair(); 39 | } 40 | var dnskey = new DNSKey(type, pair.getPublic(true, 'hex'), pair.getPrivate('hex')); 41 | var key = { dnskey: dnskey }; 42 | await this.self.repo.keystore.put(new Key(name), CborDag.util.serialize(key)); 43 | } 44 | /** 45 | * @param {String} name 46 | * @returns {Promise} 47 | */ 48 | async get(name) { 49 | if(await this.self.repo.keystore.has(new Key(name))) { 50 | const buf = await this.self.repo.keystore.get(new Key(name)); 51 | return CborDag.util.deserialize(buf); 52 | } 53 | return null; 54 | } 55 | /** 56 | * Gets public key as hex string 57 | * @param {String} name 58 | */ 59 | async getPublicKey(name) { 60 | var key = await this.get(name); 61 | if(key === null) { 62 | return null; 63 | } 64 | return key.publickey; 65 | } 66 | /** 67 | * Get key as DNSKey 68 | * @param {String} name 69 | */ 70 | async getDNSKey(name) { 71 | var key = await this.get(name); 72 | if(key === null) { 73 | return null; 74 | } 75 | return DNSKey.cast(key.dnskey); 76 | } 77 | /** 78 | * 79 | * @param {String} name 80 | * @param {DNSKey} dnskey 81 | */ 82 | async set(name, dnskey) { 83 | await this.self.repo.keystore.put(new Key(name), CborDag.util.serialize(dnskey)); 84 | } 85 | /** 86 | * 87 | * @param {String} name 88 | */ 89 | async delete(name) { 90 | await this.self.repo.keystore.delete(new Key(name)); 91 | } 92 | /** 93 | * renames orbitdns key 94 | * @param {String} name 95 | * @param {String} newname 96 | */ 97 | async rename(name, newname) { 98 | if(!(await this.self.repo.keystore.has(new Key(name)))) { 99 | return null; 100 | } 101 | var key = await this.self.repo.keystore.get(new Key(name)); 102 | await this.self.repo.keystore.delete(new Key(name)); 103 | await this.self.repo.keystore.put(new Key(newname), key); 104 | } 105 | async list() { 106 | let res = []; 107 | for await (const q of this.self.repo.keystore.query({keysOnly:true})) { 108 | res.push(q.key.toString().replace("/","")) 109 | } 110 | return res; 111 | } 112 | /** 113 | * Creates a BIP39 memoric seed. 114 | * Make sure to backup this key. 115 | * @returns {String} 116 | */ 117 | seed() { 118 | return bip39.generateMnemonic(); 119 | } 120 | } 121 | module.exports = key; -------------------------------------------------------------------------------- /src/core/core.js: -------------------------------------------------------------------------------- 1 | const { EventEmitter } = require("events"); 2 | const mergeOptions = require('merge-options') 3 | const path = require('path') 4 | const Repo = require('./repo') 5 | const os = require('os') 6 | const components = require('./components/') 7 | const OrbitName = require('./types/DataTypes').OrbitName; 8 | 9 | class Api { 10 | constructor(Core) { 11 | this.core = Core; 12 | } 13 | } 14 | /** 15 | * Core working software bundle. 16 | */ 17 | class Core extends EventEmitter { 18 | constructor(options) { 19 | super(); 20 | 21 | const defaults = { 22 | diretory: path.join(os.homedir(), '.orbitdns') 23 | }; 24 | 25 | this._options = mergeOptions(defaults, options); 26 | this.repo = new Repo(this._options.diretory); 27 | this.key = new components.key(this); 28 | this.config = new components.config(this, this._options.diretory); 29 | this.domain = new components.domain(this); 30 | this.version = components.version(this) 31 | 32 | const onReady = () => { 33 | this.removeListener('error', onError) 34 | this._ready = true 35 | } 36 | const onError = err => { 37 | this.removeListener('ready', onReady) 38 | this._readyError = err 39 | } 40 | this.once('ready', onReady).once('error', onError) 41 | this.emit('ready'); 42 | this._ready = true; 43 | this._running = false; 44 | } 45 | get ready() { 46 | return new Promise((resolve, reject) => { 47 | if (this._ready) return resolve(this) 48 | if (this._readyError) return reject(this._readyError) 49 | this.once('ready', () => resolve(this)) 50 | this.once('error', reject) 51 | }) 52 | } 53 | /** 54 | * Initializes a new OrbitDNS data folder. 55 | * @param {Object} config 56 | */ 57 | async init(config) { 58 | await this.repo.init(); 59 | await this.config.init() 60 | } 61 | async start() { 62 | 63 | try { 64 | await this.repo.open(); 65 | await this.config.open() 66 | } catch(err) { 67 | 68 | console.log(err) 69 | this.emit("error", err) 70 | return; 71 | } 72 | 73 | //this.orbitdns = await OrbitDNS.openFromAddress(this.repo.ipfs, this.config.get('netID'), 74 | //null, null, this.repo.orbitdb) 75 | try { 76 | this.db = await components.db(this); 77 | } catch(err) { 78 | console.log(err) 79 | } 80 | this.emit("start") 81 | this._running = true; 82 | } 83 | async close() { 84 | await this.repo.close(); 85 | await this.config.save(); 86 | this._running = false; 87 | } 88 | /** 89 | * Alias for close; 90 | */ 91 | async stop() { 92 | await this.close(); 93 | } 94 | /** 95 | * @param {String|OrbitName} domain 96 | * @param {Object} options format: orbitname or raw, type: A, SOA, etc 97 | */ 98 | async get(domain, options) { 99 | options = options || {}; 100 | const {type, format} = options; 101 | 102 | let name; 103 | if(OrbitName.is(domain)) { 104 | name = domain; 105 | } else { 106 | name = OrbitName.fromDnsName(domain); 107 | } 108 | if(type) { 109 | name.setType(type) 110 | } else { 111 | name.setType("SOA"); 112 | } 113 | 114 | 115 | 116 | } 117 | /** 118 | * @param {String|OrbitName} domain 119 | * @param {Object} options 120 | */ 121 | async resolve(domain, options) { 122 | options = options || {}; 123 | const {type, format} = options; 124 | 125 | let name; 126 | if(OrbitName.is(domain)) { 127 | name = domain; 128 | } else { 129 | name = OrbitName.fromDnsName(domain); 130 | } 131 | if(type) { 132 | name.setType(type) 133 | } else { 134 | name.setType("A"); 135 | } 136 | 137 | var result = await this.db.get(name.toOrbitName()) 138 | if(format === "targets" && type !== "SOA") { 139 | var out = []; 140 | for(var value of result) { 141 | out.push(value.record.target) 142 | } 143 | return out; 144 | } else { 145 | if(result.length === 1) { 146 | return result[0] 147 | } 148 | return result; 149 | } 150 | } 151 | } 152 | module.exports = Core; -------------------------------------------------------------------------------- /src/http/index.js: -------------------------------------------------------------------------------- 1 | const multiaddr = require('multiaddr') 2 | const toMultiaddr = require('uri-to-multiaddr') 3 | const Hapi = require('@hapi/hapi') 4 | const debug = require('debug') 5 | const Pino = require('hapi-pino') 6 | const errorHandler = require('./error-handler') 7 | const LOG = 'orbitdns:http-api' 8 | const LOG_ERROR = 'orbitdns:http-api:error' 9 | 10 | function hapiInfoToMultiaddr(info) { 11 | let hostname = info.host 12 | let uri = info.uri 13 | // ipv6 fix 14 | if (hostname.includes(':') && !hostname.startsWith('[')) { 15 | // hapi 16 produces invalid URI for ipv6 16 | // we fix it here by restoring missing square brackets 17 | hostname = `[${hostname}]` 18 | uri = uri.replace(`://${info.host}`, `://${hostname}`) 19 | } 20 | return toMultiaddr(uri) 21 | } 22 | 23 | 24 | async function serverCreator(serverAddrs, createServer, ipfs) { 25 | serverAddrs = serverAddrs || [] 26 | // just in case the address is just string 27 | serverAddrs = Array.isArray(serverAddrs) ? serverAddrs : [serverAddrs] 28 | 29 | const servers = [] 30 | for (const address of serverAddrs) { 31 | const addrParts = address.split('/') 32 | const server = await createServer(addrParts[2], addrParts[4], ipfs) 33 | await server.start() 34 | server.info.ma = hapiInfoToMultiaddr(server.info) 35 | servers.push(server) 36 | } 37 | return servers 38 | } 39 | class HttpApi { 40 | constructor (orbitdns, options) { 41 | this._orbitdns = orbitdns 42 | this._options = options || {} 43 | this._log = debug(LOG) 44 | this._log.error = debug(LOG_ERROR) 45 | 46 | if (process.env.ORBITDNS_MONITORING) { 47 | // Setup debug metrics collection 48 | const prometheusClient = require('prom-client') 49 | const prometheusGcStats = require('prometheus-gc-stats') 50 | const collectDefaultMetrics = prometheusClient.collectDefaultMetrics 51 | collectDefaultMetrics({ timeout: 5000 }) 52 | prometheusGcStats(prometheusClient.register)() 53 | } 54 | } 55 | 56 | async start () { 57 | this._log('starting') 58 | 59 | const orbitdns = this._orbitdns 60 | 61 | const config = await orbitdns.config.config; 62 | const apiAddrs = config.Addresses.API; 63 | //const apiAddrs = "/ip4/127.0.0.1/tcp/5001"; 64 | this._apiServers = await serverCreator(apiAddrs, this._createApiServer, orbitdns) 65 | 66 | this._log('started') 67 | return this 68 | } 69 | 70 | async _createApiServer (host, port, orbitdns) { 71 | const server = Hapi.server({ 72 | host, 73 | port, 74 | // CORS is enabled by default 75 | // TODO: shouldn't, fix this 76 | routes: { 77 | cors: true 78 | }, 79 | // Disable Compression 80 | // Why? Streaming compression in Hapi is not stable enough, 81 | // it requires bug-prone hacks such as https://github.com/hapijs/hapi/issues/3599 82 | compression: false 83 | }) 84 | server.app.orbitdns = orbitdns 85 | 86 | await server.register({ 87 | plugin: Pino, 88 | options: { 89 | prettyPrint: process.env.NODE_ENV !== 'production', 90 | logEvents: ['onPostStart', 'onPostStop', 'response', 'request-error'], 91 | level: debug.enabled(LOG) ? 'debug' : (debug.enabled(LOG_ERROR) ? 'error' : 'fatal') 92 | } 93 | }) 94 | 95 | const setHeader = (key, value) => { 96 | server.ext('onPreResponse', (request, h) => { 97 | const { response } = request 98 | if (response.isBoom) { 99 | response.output.headers[key] = value 100 | } else { 101 | response.header(key, value) 102 | } 103 | return h.continue 104 | }) 105 | } 106 | 107 | // Set default headers 108 | setHeader('Access-Control-Allow-Headers', 109 | 'X-Stream-Output, X-Chunked-Output, X-Content-Length') 110 | setHeader('Access-Control-Expose-Headers', 111 | 'X-Stream-Output, X-Chunked-Output, X-Content-Length') 112 | 113 | server.route(require('./api/routes')) 114 | 115 | errorHandler(server) 116 | 117 | return server 118 | } 119 | get apiAddr () { 120 | if (!this._apiServers || !this._apiServers.length) { 121 | throw new Error('API address unavailable - server is not started') 122 | } 123 | return multiaddr('/ip4/127.0.0.1/tcp/' + this._apiServers[0].info.port) 124 | } 125 | 126 | async stop () { 127 | this._log('stopping') 128 | const stopServers = servers => Promise.all((servers || []).map(s => s.stop())) 129 | await Promise.all([ 130 | stopServers(this._apiServers) 131 | ]) 132 | this._log('stopped') 133 | return this; 134 | } 135 | } 136 | 137 | module.exports = HttpApi 138 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | # OrbitDNS 7 | OrbitDNS is a experiment, distributed, cryptographically signed DNS replacement using IPFS and OrbitDB as record store. 8 | All records are signed and validated across the network. 9 | Networks are individualized, there is no central huge OrbitDNS network, a main network ran by the communiy will be established. Each network will have a staatic root file that contains the DNSKeys for root TLDs. (This may change at some point). Normal DNS TLDs are not allowed to be registed inside yggdns, and will be rejected if proposed. 10 | 11 | Supported DNS record types (in progress or completed) 12 | ``` 13 | SOA 14 | A 15 | AAAA 16 | TXT 17 | NS (Inprogress) 18 | MX (Inprogress) 19 | SRV (Inprogress) 20 | CNAME (Inprogress) 21 | ``` 22 | 23 | How traditional DNS records are treated in OrbitDNS 24 | OrbitDNS will implement DNS records in a different way than traditional. 25 | But to a normal DNS client will look and feel similar or exactly the same as DNS. 26 | 27 | SOA contains a DNS signing key for other records and subdomains. All subdomains are assumed to have the same parent DNSKey, unless an SOA is specifically signed to that subdomain. 28 | NS meant to delegate authority to either another OrbitDNS domain or a clearnet server, the resolver that translates OrbitDB records into DNS responses will handle these records. This record type may change in the future (WIP). 29 | CNAME works the same, except resolution of traditional DNS uses upstream server 30 | All other record types should functon the same or similar with light deviations. 31 | 32 | ### Defintions and concepts: 33 | * Resolver: A DNS server operating on port 53, that resolves OrbitDNS and clearnet records. 34 | * DNS over libp2p: Custom DNS protocol atop libp2p (Planned) 35 | * Root: record that verifies TLD SOA authority. 36 | 37 | # Installation 38 | 39 | Git installation 40 | ``` 41 | git clone https://github.com/vaultec81/orbit-dns 42 | cd orbit-dns 43 | npm install 44 | ``` 45 | 46 | npm 47 | ``` 48 | npm install orbitdns 49 | ``` 50 | 51 | ## Usage 52 | 53 | ```sh 54 | orbitdns init #Create new repo 55 | orbitdns daemon #Start daemon, with API on port 6001 56 | orbitdns help #Help guide 57 | ``` 58 | 59 | # Roadmap 60 | (core) 61 | * Better record verification, arbitary data cannot be added into record. 62 | * Better handling for root record verification. Consensus protocol for adding new TLDs on the fly. 63 | * More record support, NS, MX, SRV, CNAME. Recursive resolution 64 | * Handling for consensus protocol modification. For example adding, DNSKey specific/tiered permission, new TLDs, or other important changes to improve OrbitDNS.
65 | 66 | (external/useability) 67 | * Well built CLI client and server interface 68 | * HTTP API. APIs in other languages... OrbitDNS in golang. 69 | * Support for browser or sandboxed instance 70 | 71 | # Technical details. 72 | Each computer operating OrbitDNS will have a OrbitDNS folder containing all the neccessary files.
73 | Default folder is ~/.orbitdns
74 | structure:
75 | ``` 76 | /keystore/ - DNSKeys and other cryptographic keys in ipfs-fs-datastore 77 | /orbitdb/ - OrbitDB database, docstore. 78 | /ipfs/ - IPFS data, different than default settings to ensure zero conflict with existing jsipfs node. 79 | /root/ - (Subject to Change) contains a list of root TLDs, must be manually added via CLI or text editor. ~~This may migrate into a KV store with automatic updating. Additional security measures may be added in the future.~~ DatastoreFs KVStore 80 | /datastore - datastoreFS keyvalue store, contains API specific data and domain authorities. 81 | /config - JSON configuration file for OrbitDNS. Includes information such as network ID 82 | ``` 83 | 84 | 85 | 86 | **Deligated signature records**: 87 | Currently when signatures are made in OrbitDNS, only base58 encoded signature and hex publickey are baked into a signed record. Deligated signatures records will denote who exactly signed the record, instead of what public signed the record. 88 | For example: .tld signs example.tld SOA, the signature will represented as .tld has signed example.tld record with public key A. Public key A can then be compared to valid public keys for .tld. This makes integration with NS record type, meant to delegation authority to another domain possible. (Currently not implemented) 89 | 90 | **Multi-signature**: 91 | In many cryptocurrencies, there is the concept of requiring multiple public keys to sign in order for the transaction or record to be valid. OrbitDNS will take a similar 92 | Multi-signature keys are made up of 2 or more public keys. The publickeys are in a list, there is a requirement factor which tells others how many keys are required inorder for the signature to be valid, each publickey must sign the multi-sig key which confirms that all public keys are in agreement. This record with signatures, publickeys, minimum signature setting is then serialized and hashed into a single SHA256 Multihash. Which this can be referened as a single representation of that particular multi signature ring. (Currently not implemented) 93 | 94 | # License 95 | MIT -------------------------------------------------------------------------------- /src/core/types/DataTypes.js: -------------------------------------------------------------------------------- 1 | const EC = require('elliptic').ec; 2 | const bip39 = require('bip39') 3 | const DagCbor = require('ipld-dag-cbor'); 4 | 5 | class DataTypes { 6 | 7 | } 8 | class DNSKey { 9 | /** 10 | * 11 | * @param {String} type 12 | * @param {String} publickey 13 | * @param {String} privatekey 14 | */ 15 | constructor(type, publickey, privatekey) { 16 | this.type = type; 17 | this.publickey = publickey; 18 | this.privatekey = privatekey; 19 | } 20 | /** 21 | * Exports as CBOR dag 22 | */ 23 | toDagCbor() { 24 | return DagCbor.util.serialize(this.clean()) 25 | } 26 | /** 27 | * MUST DO BEFORE PUBLIC EXPORT! 28 | * @param {Boolean} self 29 | */ 30 | clean(self) { 31 | if(self) { 32 | delete this.privatekey; 33 | return this; 34 | } 35 | var k = DataTypes.DNSKey.cast(this); 36 | delete k.privatekey; 37 | return k; 38 | } 39 | verify(data, signature) { 40 | var ec = new EC(this.type); 41 | const pub = ec.keyFromPublic(this.publickey, 'hex') 42 | return pub.verify(data, signature) 43 | } 44 | /** 45 | * Returns keypair instance 46 | */ 47 | getPair() { 48 | var ec = new EC(this.type); 49 | const pair = ec.keyFromPrivate(this.privatekey, 'hex') 50 | return pair; 51 | } 52 | signer() { 53 | if(!this.privatekey) { 54 | return null; 55 | } 56 | var ec = new EC(this.type); 57 | const priv = ec.keyFromPrivate(this.privatekey, 'hex'); 58 | return { 59 | sign(data) { 60 | return priv.sign(data).toDER("hex") 61 | }, 62 | destroy() { 63 | //delete ec; 64 | //delete priv; 65 | } 66 | } 67 | } 68 | compare(publicKey) { 69 | return publicKey === this.publickey; 70 | } 71 | static cast(obj) { 72 | var DNSKey = new DataTypes.DNSKey(); 73 | 74 | for (var prop in obj) 75 | DNSKey[prop] = obj[prop]; 76 | return DNSKey; 77 | } 78 | static fromDagCbor(bin) { 79 | return DNSKey.cast(DagCbor.util.deserialize(bin)) 80 | } 81 | } 82 | /** 83 | * Multi sig key 84 | */ 85 | class MSK { 86 | 87 | } 88 | class CMSK { 89 | 90 | } 91 | class OrbitName { 92 | /** 93 | * 94 | * @param {String[]} nameArray 95 | * @param {Object} code 96 | */ 97 | constructor(nameArray, code) { 98 | this.name = nameArray 99 | 100 | if(code) { 101 | this.code = code; 102 | } else { 103 | this.code = {}; 104 | } 105 | } 106 | toOrbitName() { 107 | var fqdn = this.name.join("."); 108 | var array = [fqdn] 109 | if(this.code.type) { 110 | array.push("$"+this.code.type); 111 | } 112 | if(this.code.index) { 113 | array.push("#"+this.code.index) 114 | } 115 | return array.join("/") 116 | } 117 | toDnsName() { 118 | var temp = Object.assign([], this.name); temp.reverse() 119 | return temp.join() 120 | } 121 | get tld() { 122 | return this.name[0]; 123 | } 124 | get parent() { 125 | var list = [] 126 | for(var x = 0; x < this.name.length-1; x++) { 127 | list.push(this.name[x]) 128 | } 129 | return new OrbitName(list, this.code) 130 | } 131 | /** 132 | * 133 | * @param {String} type 134 | */ 135 | setType(type) { 136 | this.code.type = type.toUpperCase(); 137 | return this; 138 | } 139 | setIndex(index) { 140 | this.code.index = index; 141 | return this; 142 | } 143 | isTLD() { 144 | if(this.name.length === 1) { 145 | return true; 146 | } 147 | return false; 148 | } 149 | /** 150 | * Converts from normal DNS syntax (example.com) 151 | * @param {String} name 152 | * @returns {OrbitName} 153 | */ 154 | static fromDnsName(name) { 155 | var array = name.split(".") 156 | array.reverse() 157 | return new OrbitName(array); 158 | } 159 | /** 160 | * Converts from yggName syntax (com.exmple) 161 | * @param {String} name 162 | * @returns {OrbitName} 163 | */ 164 | static fromOrbitName(name) { 165 | var deslash = name.split("/"); 166 | var array = deslash[0].split(".");deslash.reverse(); deslash.pop(); 167 | var code = {}; 168 | for(var value of deslash) { 169 | if(value[0] === "$") { 170 | code.type = value.split("$")[1]; 171 | } 172 | if(value[0] === "#") { 173 | code.index = value.split("#")[1]; 174 | } 175 | } 176 | return new OrbitName(array, code); 177 | } 178 | static cast(obj) { 179 | var r = new OrbitName(); 180 | 181 | for (var prop in obj) 182 | r[prop] = obj[prop]; 183 | return r; 184 | } 185 | static is(obj) { 186 | return obj instanceof OrbitName; 187 | } 188 | } 189 | /** 190 | * Special class to create a object representing a certain DNS operation or change. 191 | * Can be serialized and sent over IPC or HTTP API. 192 | */ 193 | DataTypes.DNSAction = class { 194 | 195 | } 196 | exports = module.exports = DataTypes; 197 | exports.DNSKey = DNSKey; 198 | exports.OrbitName = OrbitName; -------------------------------------------------------------------------------- /src/core/types/RecordTypes.js: -------------------------------------------------------------------------------- 1 | const SignedObject = require('./SignedObject') 2 | 3 | const base_list = [ 4 | "SOA", 5 | "A", 6 | "AAAA", 7 | "TXT", 8 | "NS", 9 | "MX", 10 | "SRV", 11 | "CNAME" 12 | ] 13 | class RecordTypes { 14 | 15 | } 16 | class BaseRecord { 17 | toSignedObject() { 18 | return SignedObject.cast(this) 19 | } 20 | } 21 | RecordTypes.A = class extends BaseRecord { 22 | constructor() { 23 | super(); 24 | this.type = "A"; 25 | } 26 | validate() { 27 | var re = new RegExp('((25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)(\.|$)){4}'); 28 | if(re.test(this.record.target) === false) { 29 | return false; 30 | } 31 | return true; 32 | } 33 | static create(name, target, options) { 34 | const { ttl } = options; 35 | var r = new RecordTypes.A(); 36 | r.name = name; 37 | var record = {}; 38 | record.ttl = ttl; 39 | record.target = target; 40 | r.record = record; 41 | return r; 42 | } 43 | static cast(obj) { 44 | var r = new RecordTypes.A(); 45 | 46 | for (var prop in obj) 47 | r[prop] = obj[prop]; 48 | return r; 49 | } 50 | 51 | } 52 | RecordTypes.AAAA = class extends BaseRecord { 53 | constructor() { 54 | super() 55 | this.type = "AAAA"; 56 | } 57 | validate() { 58 | var re = new RegExp("(([0-9a-fA-F]{1,4}:){7,7}[0-9a-fA-F]{1,4}|([0-9a-fA-F]{1,4}:){1,7}:|([0-9a-fA-F]{1,4}:){1,6}:[0-9a-fA-F]{1,4}|([0-9a-fA-F]{1,4}:){1,5}(:[0-9a-fA-F]{1,4}){1,2}|([0-9a-fA-F]{1,4}:){1,4}(:[0-9a-fA-F]{1,4}){1,3}|([0-9a-fA-F]{1,4}:){1,3}(:[0-9a-fA-F]{1,4}){1,4}|([0-9a-fA-F]{1,4}:){1,2}(:[0-9a-fA-F]{1,4}){1,5}|[0-9a-fA-F]{1,4}:((:[0-9a-fA-F]{1,4}){1,6})|:((:[0-9a-fA-F]{1,4}){1,7}|:)|fe80:(:[0-9a-fA-F]{0,4}){0,4}%[0-9a-zA-Z]{1,}|::(ffff(:0{1,4}){0,1}:){0,1}((25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}[0-9])\.){3,3}(25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}[0-9])|([0-9a-fA-F]{1,4}:){1,4}:((25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}[0-9])\.){3,3}(25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}[0-9]))") 59 | if(re.test(this.record.target) === false) { 60 | return false; 61 | } 62 | return true; 63 | } 64 | static create(name, target, options) { 65 | const { ttl } = options; 66 | var r = new RecordTypes.AAAA(); 67 | r.name = name; 68 | var record = {}; 69 | record.ttl = ttl; 70 | record.target = target; 71 | r.record = record; 72 | return r; 73 | } 74 | static cast(obj) { 75 | var r = new RecordTypes.AAAA(); 76 | 77 | for (var prop in obj) 78 | r[prop] = obj[prop]; 79 | return r; 80 | } 81 | } 82 | RecordTypes.TXT = class extends BaseRecord { 83 | constructor() { 84 | super(); 85 | this.type = "TXT"; 86 | } 87 | validate() { 88 | if(record.data.length > 255) { 89 | return false; 90 | } 91 | return true; 92 | } 93 | static create(name, data, options) { 94 | const { ttl } = options; 95 | var r = new RecordTypes.TXT(); 96 | r.name = name; 97 | var record = {}; 98 | record.ttl = ttl; 99 | record.data = data 100 | r.record = record; 101 | return r; 102 | } 103 | static cast(obj) { 104 | var r = new RecordTypes.TXT(); 105 | 106 | for (var prop in obj) 107 | r[prop] = obj[prop]; 108 | return r; 109 | } 110 | } 111 | RecordTypes.SOA = class extends BaseRecord { 112 | constructor() { 113 | super(); 114 | this.type = "SOA"; 115 | } 116 | /** 117 | * Determines whether record is valid or not. 118 | * @returns {Boolean} 119 | */ 120 | validate() { 121 | if(this.DNSKeys.length === 0) { 122 | return false; 123 | } 124 | return true; 125 | } 126 | isInDNSKeys(publickey) { 127 | for(var value of this.DNSKeys) { 128 | if(value.publickey === publickey) { 129 | return true; 130 | } 131 | } 132 | return false; 133 | } 134 | static create(name, DNSKeys, extra) { 135 | const { serial, expire, ttl } = extra; 136 | var r = new RecordTypes.SOA() 137 | r.name = name; //FQDN or TLD. 138 | r.DNSKeys = DNSKeys; //Array 139 | var record = {}; 140 | record.serial = serial; 141 | record.expire = expire; 142 | record.ttl = ttl; 143 | r.record = record; 144 | return r; 145 | } 146 | static cast(obj) { 147 | var r = new RecordTypes.SOA(); 148 | for (var prop in obj) 149 | r[prop] = obj[prop]; 150 | return r; 151 | } 152 | } 153 | RecordTypes.MX = class extends BaseRecord { 154 | constructor() { 155 | super(); 156 | this.type = "MX"; 157 | } 158 | static cast(obj) { 159 | var r = new RecordTypes.MX(); 160 | 161 | for (var prop in obj) 162 | r[prop] = obj[prop]; 163 | return r; 164 | } 165 | } 166 | RecordTypes.CNAME = class extends BaseRecord { 167 | constructor() { 168 | super(); 169 | this.type = "CNAME"; 170 | } 171 | static create(name, target, options) { 172 | const { ttl } = options; 173 | var r = new RecordTypes.CNAME(); 174 | r.name = name; 175 | var record = {}; 176 | record.ttl = ttl; 177 | record.target = target; 178 | r.record = record; 179 | return r; 180 | } 181 | static cast(obj) { 182 | var r = new RecordTypes.CNAME(); 183 | 184 | for (var prop in obj) 185 | r[prop] = obj[prop]; 186 | return r; 187 | } 188 | } 189 | exports = module.exports = RecordTypes; -------------------------------------------------------------------------------- /src/core/components/domain.js: -------------------------------------------------------------------------------- 1 | const Core = require("../core") 2 | const OrbitName = require('../types/').DataTypes.OrbitName; 3 | const IDatastore = require('interface-datastore') 4 | const Key = IDatastore.Key 5 | const RecordTypes = require('../types/DataTypes') 6 | const DNSKey = require('../types/DataTypes').DNSKey; 7 | 8 | /** 9 | * Domain creation request 10 | * This is sent to root TLD 11 | */ 12 | class dcr { 13 | constructor(domain, DNSKeys) { 14 | 15 | } 16 | } 17 | class domain { 18 | /** 19 | * 20 | * @param {Core} self 21 | */ 22 | constructor(self) { 23 | this.self = self; 24 | this.transfers = {}; 25 | } 26 | /** 27 | * Gets domain from local domain store. 28 | * Only domains that are locally imported, and thus have authority over. 29 | * @param {String|OrbitName} domain 30 | */ 31 | async get(domain) { 32 | let txtName; 33 | if (OrbitName.is(domain)) { 34 | txtName = domain.toDnsName(); 35 | } else { 36 | txtName = domain; 37 | } 38 | 39 | return await this.self.repo.datastore.get(new Key("domain/" + txtName)) 40 | } 41 | /** 42 | * Resolves DNS record to entry. 43 | * @todo Add support for internal CNAME. recursive option. 44 | * @param {String|OrbitName} domain 45 | * @param {Object} options 46 | */ 47 | async resolve(domain, options) { 48 | options = options || {}; 49 | const { type, format, recursive } = options; 50 | 51 | let name; 52 | if (OrbitName.is(domain)) { 53 | name = domain; 54 | } else { 55 | name = OrbitName.fromDnsName(domain); 56 | if (type) { 57 | name.setType(type); 58 | } else { 59 | name.setType("SOA"); 60 | } 61 | } 62 | if (!(await this.self.repo.root.has(name.tld))) { 63 | return null; 64 | } 65 | var result = await this.self.db.get(name.toOrbitName()); 66 | if (recursive) { 67 | for (var record of result) { 68 | console.log(record) 69 | } 70 | } 71 | if (result.length === 1) { 72 | return result[0]; 73 | } else { 74 | return result; 75 | } 76 | 77 | } 78 | /*async create(domain, key) { 79 | let txtName; 80 | if (OrbitName.is(domain)) { 81 | txtName = domain.toDnsName() 82 | } else if (typeof domain === "string") { 83 | txtName = domain; 84 | } else { 85 | throw `Invalid arguments domain is ${domain}` 86 | } 87 | 88 | }*/ 89 | /** 90 | * List domain that have been registered with this node. 91 | */ 92 | async list() { 93 | var it = this.self.repo.datastore.query({ keysOnly: true, prefix:"domain"}); 94 | var out = []; 95 | for await (let val of it) { 96 | out.push(val.key._buf.toString().replace("/", "")) 97 | } 98 | return out; 99 | } 100 | /** 101 | * List records of a particular domain. 102 | * Does not return full record. 103 | * @param {String|OrbitName} domain 104 | */ 105 | async listRecords(domain) { 106 | let name; 107 | if (OrbitName.is(domain)) { 108 | name = domain; 109 | } else { 110 | name = OrbitName.fromDnsName(domain); 111 | } 112 | var records = {}; 113 | var result = await this.self.db.get(name.toOrbitName()) 114 | for (var value of result) { 115 | if (!records[value.type]) { 116 | records[value.type] = []; 117 | } 118 | var rname = OrbitName.fromOrbitName(value.id); 119 | if (!rname.code.index) { 120 | records[value.type].push(0) 121 | } else { 122 | records[value.type].push(rname.code.index) 123 | } 124 | } 125 | return records; 126 | } 127 | /** 128 | * Originally copy from the experimental set of code 129 | * Pleast test functionality. 130 | * @param {OrbitName} name 131 | * @param {Boolean} skipFirst 132 | */ 133 | async findParentSOA(name, skipFirst) { 134 | var name1 = DataTypes.OrbitName.cast(name) 135 | name1.setType("SOA"); //Ensure looking for SOA record 136 | do { 137 | if (skipFirst) { 138 | name1 = name1.parent 139 | } 140 | console.log(name1) 141 | var entry = (await this.db.get(name1.toOrbitName()))[0] 142 | if (entry) { 143 | return RecordTypes.SOA.cast(entry) 144 | } 145 | name1 = name1.parent 146 | } while (name1.name.length !== 0) 147 | return null; //if no parent SOA could be found. 148 | } 149 | /** 150 | * 151 | * @param {String|OrbitName} domain 152 | * @param {String} key string name of local DNSKey 153 | */ 154 | async create(domain, key) { 155 | let txtName; 156 | if (OrbitName.is(domain)) { 157 | txtName = domain.toDnsName() 158 | } else if (typeof domain === "string") { 159 | txtName = domain; 160 | } else { 161 | throw `Invalid arguments domain is ${domain}` 162 | } 163 | 164 | return ({ 165 | 166 | }) 167 | } 168 | get record() { 169 | /** 170 | * 171 | * @param {String|OrbitName} domain 172 | * @param {String} target 173 | * @param {String} type 174 | * @param {Object} options 175 | */ 176 | async function add(domain, target, type, options) { 177 | const { key } = options; 178 | if (type === "SOA" | !this.RecordTypes[type]) { 179 | throw `${type} is not an accepted type`; 180 | } 181 | if (typeof domain === "string") { 182 | domain = OrbitName.fromDnsName(domain); 183 | } 184 | /** 185 | * @type {DNSkey} 186 | */ 187 | let dnskey; 188 | if (key) { 189 | dnskey = this.self.keystore.get(key) 190 | } else if (options.dnskey) { 191 | dnskey = options.dnskey; 192 | } else { 193 | throw `dnskey or key name required`; 194 | } 195 | var record = new recordTypes[type](domain, target, { ttl: 360 }); 196 | dnskey. 197 | this.self.db.put() 198 | } 199 | /** 200 | * 201 | * @param {String|OrbitName} domain 202 | * @param {String} target 203 | * @param {String} type 204 | * @param {Number} index 205 | * @param {Object} options 206 | */ 207 | function set(domain, target, type, index, options) { 208 | const { key } = options; 209 | if (!index) { 210 | throw "index is required in options"; 211 | } 212 | if (typeof domain === "string") { 213 | domain = OrbitName.fromDnsName(domain); 214 | } 215 | let dnskey; 216 | if (key) { 217 | dnskey = this.self.keystore.get(key) 218 | } else if (options.dnskey) { 219 | dnskey = options.dnskey; 220 | } else { 221 | throw ``; 222 | } 223 | } 224 | const funcs = { 225 | set: set, 226 | add: add 227 | } 228 | return (funcs) 229 | } 230 | /** 231 | * Preform a domain transfer. 232 | * Integration with HTTP API 233 | * @param {String|OrbitName} domain 234 | * @param {Object} options 235 | */ 236 | async transfer(domain, options) { 237 | if (this.transfers[domain.toDnsName()]) { 238 | return this.transfers[domain.toDnsName()]; 239 | } 240 | /** 241 | * Removes ownership of domain, local DNSkeys will be removed from domain. 242 | */ 243 | const { removeOwnership } = options; 244 | var SOA = await this.get(domain, { type: "SOA" }); 245 | 246 | var owners = SOA.DNSKeys; 247 | /** 248 | * @param {String|Number} id 249 | */ 250 | owners.remove = (id) => { 251 | if (typeof id === "string") { 252 | for (var n in owners) { 253 | if (owners[n].publickey === id) { 254 | delete owners[n]; 255 | } 256 | } 257 | } else if (typeof id === "number") { 258 | delete owners[id] 259 | } 260 | } 261 | return ({ 262 | owners: owners, 263 | add() { 264 | 265 | }, 266 | /** 267 | * Execute domain transfer. 268 | */ 269 | execute() { 270 | 271 | } 272 | }) 273 | } 274 | } 275 | module.exports = domain; -------------------------------------------------------------------------------- /src/core/AccessController.js: -------------------------------------------------------------------------------- 1 | //const AccessController = require('orbit-db-access-controllers') 2 | const pMapSeries = require('p-map-series') 3 | const path = require('path') 4 | // Make sure the given address has '/_access' as the last part 5 | const ensureAddress = address => { 6 | const suffix = address.toString().split('/').pop() 7 | return suffix === '_access' 8 | ? address 9 | : path.join(address, '/_access') 10 | } 11 | 12 | const EventEmitter = require('events').EventEmitter 13 | const DataTypes = require('./types/DataTypes') 14 | const RecordTypes = require('./types/RecordTypes') 15 | const SignedObject = require('./types/SignedObject') 16 | 17 | class Validator { 18 | /** 19 | * 20 | * @param {*} store OrbitDB Documentstore 21 | * @param {Object} root 22 | */ 23 | constructor(store, root) { 24 | this.store = store; 25 | this.root = root; 26 | this.tldCache = {}; 27 | } 28 | /** 29 | * 30 | * @param {DataTypes.OrbitName} name 31 | * @param {Boolean} skipFirst 32 | * @returns {RecordTypes.SOA} 33 | */ 34 | _findParentWithSOA(name, skipFirst) { 35 | var name1 = DataTypes.OrbitName.cast(name) 36 | name1.setType("SOA"); //Ensure looking for SOA record 37 | do { 38 | if(skipFirst) { 39 | name1 = name1.parent 40 | } 41 | var entry = this.store.get(name1.toOrbitName())[0] 42 | if(entry) { 43 | return RecordTypes.SOA.cast(entry) 44 | } 45 | name1 = name1.parent 46 | } while(name1.name.length !== 0) 47 | return null; //if no parent SOA could be found. 48 | } 49 | /** 50 | * Validate domain without validating TLD -> SOA authenticity. 51 | * Example check: AAAA -> example.ygg SOA. 52 | * Lowest level check 53 | * @param {Object} entry 54 | */ 55 | validateName(entry) { 56 | var name = DataTypes.OrbitName.fromOrbitName(entry.id); 57 | var soa = this._findParentWithSOA(name); 58 | var record = RecordTypes[record.type].cast(entry) 59 | var publickey = entry.$publickey; 60 | if(!record.toSignedObject().validate(publickey)) { 61 | return false; 62 | } 63 | //Test for record validately reguardless of record type (A, AAAA, TXT etc) 64 | if(!record.validate()) { 65 | return false; 66 | } 67 | if(!soa.isInDNSKeys(publickey)) { 68 | return false; 69 | } 70 | return true; 71 | } 72 | /** 73 | * Exp check: example.ygg SOA -> .ygg TLD SOA 74 | * Grabs old SOA record to check whether, 75 | * @param {Object} entry 76 | */ 77 | validateSOA(entry) { 78 | var name = DataTypes.OrbitName.fromOrbitName(entry.id) 79 | var soa = RecordTypes.SOA.cast(entry) 80 | var parent_soa = this._findParentWithSOA(name) 81 | 82 | //Signature check 83 | var validSig = SignedObject.cast(entry).validate(entry.$publickey); 84 | var publickey = entry.$publickey; 85 | if(!validSig) { 86 | return false; 87 | } 88 | 89 | if(!soa.validate()) { 90 | return false; //Valid SOA, ensure SOA does not violate rules. 91 | } 92 | var past_soa = this.store.get(name.toOrbitName())[0]; //Get SOA; 93 | if(past_soa) { 94 | 95 | past_soa = RecordTypes.SOA.cast(past_soa); 96 | 97 | if(past_soa.record.expire !== entry.record.expire) { 98 | //Instant rejection for attempting to change expiration. 99 | return false; 100 | } 101 | } 102 | 103 | return true; 104 | } 105 | /** 106 | * Checks validately of TLD 107 | * @param {Object} entry 108 | * @param {String} tld 109 | */ 110 | async validateTLD(entry) { 111 | var orbitName = DataTypes.OrbitName.fromOrbitName(entry.id); 112 | var soa = RecordTypes.SOA.cast(entry); 113 | if(!soa.validate()) { 114 | return false; 115 | } 116 | if(!soa.toSignedObject().validate(soa.$publickey)) { 117 | return false; 118 | } 119 | 120 | if(!soa.isInDNSKeys(soa.$publickey)) { 121 | return false; 122 | } 123 | //if(!this.root[orbitName.toDnsName()]) { 124 | // return false; 125 | //} 126 | /*var a = false; 127 | var keys = await this.root(orbitName.toDnsName()) 128 | for(var key of keys) { 129 | if(soa.$publickey === key.publickey) { 130 | a = true; 131 | } 132 | } 133 | if(a === false) 134 | return false; 135 | */ 136 | return true; 137 | } 138 | } 139 | /** 140 | * Special validation logic for Orbit DNS, not designed for any other purpose. 141 | */ 142 | class orbitDNSAC extends EventEmitter { 143 | constructor (orbitdb, options) { 144 | super(); 145 | this._orbitdb = orbitdb 146 | this._db = null 147 | this._options = options || {} 148 | this._root = options.root; 149 | } 150 | 151 | // Returns the type of the access controller 152 | static get type () { return "orbitdns" } 153 | 154 | //Returns the address of the OrbitDB used as the AC 155 | get address () { 156 | return this._db.address 157 | } 158 | 159 | // Return true if entry is allowed to be added to the database 160 | async canAppend (entry, identityProvider) { 161 | if(entry.payload.op === "DEL") { 162 | return false; //Block deletion of records temporarily until a proper system for record deletion is created. 163 | } 164 | if(entry) 165 | console.log(entry.payload.value) 166 | for(var address in this._orbitdb.stores) { 167 | var split = address.split("/"); 168 | if(split[3] === this._options.address.split("/")[3]) { 169 | if(split[split.length - 1] !== "_access") { 170 | this.store = this._orbitdb.stores[address] 171 | } 172 | } 173 | } 174 | this.validator = new Validator(this.store, this._root) 175 | 176 | 177 | 178 | 179 | //console.log(so.type) 180 | //console.log(RecordTypes[so.type].cast(so).validate()) 181 | 182 | //Valid signature 183 | /* 184 | if(!RecordTypes[so.type].cast(so).validate() || !so.validate(so.$publickey)) { 185 | return false; 186 | } 187 | switch(so.type) { 188 | case "SOA": 189 | var contains = false; 190 | for(var key of so.DNSKeys) { 191 | if(key.publickey === key.publickey) { 192 | contains = true; 193 | } 194 | } 195 | if(!contains) { 196 | return false; 197 | } 198 | 199 | }*/ 200 | var name = DataTypes.OrbitName.fromOrbitName(entry.payload.value.id) 201 | 202 | if(!entry.payload.value.type) { 203 | return false; 204 | } 205 | var record = RecordTypes[entry.payload.value.type].cast(entry.payload.value) 206 | if(name.isTLD()) { 207 | if(!(await this.validator.validateTLD(entry.payload.value))) { 208 | return false; 209 | } 210 | } 211 | if(record.type === "SOA") { 212 | if(!this.validator.validateSOA(record)) { 213 | return false; 214 | } 215 | } else { 216 | if(!this.validator.validateName(record)) { 217 | return false; 218 | } 219 | } 220 | 221 | 222 | // Write keys and admins keys are allowed 223 | const access = new Set([...this.get('write'), ...this.get('admin')]) 224 | // If the ACL contains the writer's public key or it contains '*' 225 | if (access.has(entry.identity.id) || access.has('*')) { 226 | const verifiedIdentity = await identityProvider.verifyIdentity(entry.identity) 227 | // Allow access if identity verifies 228 | return verifiedIdentity 229 | } 230 | 231 | return false 232 | } 233 | 234 | get capabilities () { 235 | if (this._db) { 236 | const capabilities = this._db.index 237 | 238 | const toSet = (e) => { 239 | const key = e[0] 240 | capabilities[key] = new Set([...(capabilities[key] || []), ...e[1]]) 241 | } 242 | 243 | // Merge with the access controller of the database 244 | // and make sure all values are Sets 245 | Object.entries({ 246 | ...capabilities, 247 | // Add the root access controller's 'write' access list 248 | // as admins on this controller 249 | ...{ admin: new Set([...(capabilities.admin || []), ...this._db.access.write]) } 250 | }).forEach(toSet) 251 | 252 | return capabilities 253 | } 254 | return {} 255 | } 256 | 257 | get (capability) { 258 | return this.capabilities[capability] || new Set([]) 259 | } 260 | 261 | async close () { 262 | await this._db.close() 263 | } 264 | 265 | async load (address) { 266 | if (this._db) { await this._db.close() } 267 | 268 | // Force '
/_access' naming for the database 269 | this._db = await this._orbitdb.keyvalue(ensureAddress(address), { 270 | // use ipfs controller as a immutable "root controller" 271 | accessController: { 272 | type: 'ipfs', 273 | write: this._options.admin || [this._orbitdb.identity.id] 274 | }, 275 | sync: true 276 | }) 277 | 278 | this._db.events.on('ready', this._onUpdate.bind(this)) 279 | this._db.events.on('write', this._onUpdate.bind(this)) 280 | this._db.events.on('replicated', this._onUpdate.bind(this)) 281 | 282 | await this._db.load() 283 | } 284 | 285 | async save () { 286 | // return the manifest data 287 | return { 288 | address: this._db.address.toString() 289 | } 290 | } 291 | 292 | async grant (capability, key) { 293 | // Merge current keys with the new key 294 | const capabilities = new Set([...(this._db.get(capability) || []), ...[key]]) 295 | await this._db.put(capability, Array.from(capabilities.values())) 296 | } 297 | 298 | async revoke (capability, key) { 299 | const capabilities = new Set(this._db.get(capability) || []) 300 | capabilities.delete(key) 301 | if (capabilities.size > 0) { 302 | await this._db.put(capability, Array.from(capabilities.values())) 303 | } else { 304 | await this._db.del(capability) 305 | } 306 | } 307 | 308 | /* Private methods */ 309 | _onUpdate () { 310 | this.emit('updated') 311 | } 312 | 313 | /* Factory */ 314 | static async create (orbitdb, options = {}) { 315 | const ac = new orbitDNSAC(orbitdb, options) 316 | await ac.load(options.address || options.name || 'default-access-controller') 317 | 318 | // Add write access from options 319 | if (options.write && !options.address) { 320 | await pMapSeries(options.write, async (e) => ac.grant('write', e)) 321 | } 322 | 323 | return ac 324 | } 325 | } 326 | orbitDNSAC.Validator = Validator; 327 | 328 | 329 | module.exports = orbitDNSAC; --------------------------------------------------------------------------------