From 2732c3a1494b3ec75776cd6fa0dd51792e32ee44 Mon Sep 17 00:00:00 2001 From: Kevin Froman Date: Wed, 29 Jan 2020 13:45:31 -0600 Subject: [PATCH 1/9] reenable client security (OOPS) --- src/httpapi/security/client.py | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/src/httpapi/security/client.py b/src/httpapi/security/client.py index 86609d70..fbb6b288 100644 --- a/src/httpapi/security/client.py +++ b/src/httpapi/security/client.py @@ -51,13 +51,13 @@ class ClientAPISecurity: return if request.path.startswith('/site/'): return - # try: - # if not hmac.compare_digest(request.headers['token'], client_api.clientToken): - # if not hmac.compare_digest(request.form['token'], client_api.clientToken): - # abort(403) - # except KeyError: - # if not hmac.compare_digest(request.form['token'], client_api.clientToken): - # abort(403) + try: + if not hmac.compare_digest(request.headers['token'], client_api.clientToken): + if not hmac.compare_digest(request.form['token'], client_api.clientToken): + abort(403) + except KeyError: + if not hmac.compare_digest(request.form['token'], client_api.clientToken): + abort(403) @client_api_security_bp.after_app_request def after_req(resp): From d612fbf5c54c15f96309a6730ed7c5bc43c833bd Mon Sep 17 00:00:00 2001 From: Kevin Froman Date: Tue, 28 Jan 2020 00:57:33 -0600 Subject: [PATCH 2/9] better market readme for anti censorship --- README.md | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 2491c7d5..589798b8 100644 --- a/README.md +++ b/README.md @@ -30,9 +30,11 @@ Onionr ("Onion Relay") is a decentralized/distributed peer-to-peer communication network, designed to be anonymous and resistant to (meta)data analysis, spam, and corruption. -Onionr stores data in independent packages referred to as 'blocks'. The blocks are distributed to all interested nodes. Blocks and user IDs cannot be easily proven to have been created by a particular user. Even if there is enough evidence to believe that a specific user created a block, nodes still operate behind Tor or I2P and as such cannot be trivially unmasked. The anonymity is achieved by a stateless network with no given indication of what node a block came from or even the user ID. +Onionr stores data in independent packages referred to as 'blocks'. The blocks are distributed to all interested nodes. Blocks and user IDs cannot be easily proven to have been created by a particular user. Even if there is enough evidence to believe that a specific user created a block, nodes still operate behind Tor or I2P and as such cannot be trivially unmasked. Anonymity is achieved by a stateless network, with no given indication of what node a block originates from. Through message mixing and key privacy, it is intended to be nigh impossible to discover the identity of a message creator or recipient. -Through long-term traffic analysis, a well funded adversary may discover the most probable node(s) to be creating a set of related blocks, however doing so would only lead them to a node behind Tor or I2P. As the first node that a block appears on is almost always not the creator of the block, there is plausible deniability regarding the true creator of the block. +Via long-term traffic analysis, a well funded adversary may discover the most probable node(s) to be creating a set of related blocks, however doing so would only lead them to a node behind Tor or I2P. As the first node that a block appears on is almost always not the creator of the block, there is plausible deniability regarding the true creator of the block. + +Onionr gives the individual the ability to speak freely, without fear of surveillance and censorship. Users are identified by ed25519/curve25519 public keys, which can be used to sign blocks or send encrypted data. From c636e87b2cca48066d7cef0150778b5348bbae65 Mon Sep 17 00:00:00 2001 From: Kevin Froman Date: Tue, 28 Jan 2020 02:15:11 -0600 Subject: [PATCH 3/9] improve formatting in removeblock and blacklist --- src/onionrblocks/onionrblacklist.py | 31 ++++++++++++++++------------- src/onionrstorage/removeblock.py | 24 ++++++++++++++++++++-- 2 files changed, 39 insertions(+), 16 deletions(-) diff --git a/src/onionrblocks/onionrblacklist.py b/src/onionrblocks/onionrblacklist.py index f40c3e3a..544e7d2d 100755 --- a/src/onionrblocks/onionrblacklist.py +++ b/src/onionrblocks/onionrblacklist.py @@ -1,9 +1,15 @@ -''' - Onionr - Private P2P Communication +"""Onionr - Private P2P Communication. - This file handles maintenence of a blacklist database, for blocks and peers -''' -''' +Handle maintenence of a blacklist database, for blocks and peers +""" +import sqlite3 +import os + +from onionrplugins.onionrevents import event +import onionrcrypto +from onionrutils import epoch, bytesconverter +from coredb import dbfiles +""" This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or @@ -16,11 +22,8 @@ You should have received a copy of the GNU General Public License along with this program. If not, see . -''' -import sqlite3, os -import logger, onionrcrypto -from onionrutils import epoch, bytesconverter -from coredb import dbfiles +""" + class OnionrBlackList: def __init__(self): @@ -57,7 +60,7 @@ class OnionrBlackList: return def deleteExpired(self, dataType=0): - '''Delete expired entries''' + """Delete expired entries""" deleteList = [] curTime = epoch.get_epoch() @@ -78,7 +81,7 @@ class OnionrBlackList: return def clearDB(self): - self._dbExecute('''DELETE FROM blacklist;''') + self._dbExecute("""DELETE FROM blacklist;""") def getList(self): data = self._dbExecute('SELECT * FROM blacklist') @@ -88,11 +91,11 @@ class OnionrBlackList: return myList def addToDB(self, data, dataType=0, expire=0): - '''Add to the blacklist. Intended to be block hash, block data, peers, or transport addresses + """Add to the blacklist. Intended to be block hash, block data, peers, or transport addresses 0=block 1=peer 2=pubkey - ''' + """ # we hash the data so we can remove data entirely from our node's disk hashed = bytesconverter.bytes_to_str(onionrcrypto.hashers.sha3_hash(data)) diff --git a/src/onionrstorage/removeblock.py b/src/onionrstorage/removeblock.py index afe79bf2..49ec846f 100644 --- a/src/onionrstorage/removeblock.py +++ b/src/onionrstorage/removeblock.py @@ -1,14 +1,34 @@ +"""Onionr - Private P2P Communication. + +remove onionr block from meta db +""" import sys, sqlite3 import onionrexceptions, onionrstorage from onionrutils import stringvalidators from coredb import dbfiles from onionrblocks import storagecounter +""" + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . +""" + + def remove_block(block): - ''' + """ remove a block from this node (does not automatically blacklist) **You may want blacklist.addToDB(blockHash) - ''' + """ if stringvalidators.validate_hash(block): conn = sqlite3.connect(dbfiles.block_meta_db, timeout=30) From a958b99fefc499d5ad20fb684fda7d292f7b981e Mon Sep 17 00:00:00 2001 From: Kevin Froman Date: Tue, 28 Jan 2020 21:39:01 -0600 Subject: [PATCH 4/9] remove sent mail from sentbox when block is blacklisted --- src/onionrblocks/onionrblacklist.py | 5 +++- static-data/default-plugins/pms/main.py | 2 ++ .../default-plugins/pms/onblacklist.py | 12 ++++++++++ static-data/default-plugins/pms/sentboxdb.py | 23 +++++++++++-------- tests/test_onionrvalues.py | 19 +++++++++++++++ tests/test_template.py | 3 --- 6 files changed, 50 insertions(+), 14 deletions(-) create mode 100644 static-data/default-plugins/pms/onblacklist.py create mode 100644 tests/test_onionrvalues.py diff --git a/src/onionrblocks/onionrblacklist.py b/src/onionrblocks/onionrblacklist.py index 544e7d2d..a0824edf 100755 --- a/src/onionrblocks/onionrblacklist.py +++ b/src/onionrblocks/onionrblacklist.py @@ -1,6 +1,6 @@ """Onionr - Private P2P Communication. -Handle maintenence of a blacklist database, for blocks and peers +Handle maintenance of a blacklist database, for blocks and peers """ import sqlite3 import os @@ -99,6 +99,9 @@ class OnionrBlackList: # we hash the data so we can remove data entirely from our node's disk hashed = bytesconverter.bytes_to_str(onionrcrypto.hashers.sha3_hash(data)) + + event('blacklist_add', data={'data': data, 'hash': hashed}) + if len(hashed) > 64: raise Exception("Hashed data is too large") diff --git a/static-data/default-plugins/pms/main.py b/static-data/default-plugins/pms/main.py index 180b6dd8..ca1ae569 100755 --- a/static-data/default-plugins/pms/main.py +++ b/static-data/default-plugins/pms/main.py @@ -34,6 +34,8 @@ PLUGIN_VERSION = '0.0.1' sys.path.insert(0, os.path.dirname(os.path.realpath(__file__))) import sentboxdb, mailapi, loadinbox # import after path insert +from onblacklist import on_blacklist_add + flask_blueprint = mailapi.flask_blueprint security_whitelist = ['staticfiles.mail', 'staticfiles.mailindex'] diff --git a/static-data/default-plugins/pms/onblacklist.py b/static-data/default-plugins/pms/onblacklist.py new file mode 100644 index 00000000..0e521967 --- /dev/null +++ b/static-data/default-plugins/pms/onblacklist.py @@ -0,0 +1,12 @@ +from threading import Thread + +from onionrutils import localcommand + + +def on_blacklist_add(api, data=None): + blacklisted_data = data['data'] + + def remove(): + localcommand.local_command(f'/mail/deletemsg/{blacklisted_data}', post=True) + + Thread(target=remove).start() diff --git a/static-data/default-plugins/pms/sentboxdb.py b/static-data/default-plugins/pms/sentboxdb.py index fee62bce..b07445a8 100755 --- a/static-data/default-plugins/pms/sentboxdb.py +++ b/static-data/default-plugins/pms/sentboxdb.py @@ -1,9 +1,13 @@ -''' +""" Onionr - Private P2P Communication This file handles the sentbox for the mail plugin -''' -''' +""" +import sqlite3 +import os +from onionrutils import epoch +from utils import identifyhome, reconstructhash +""" This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or @@ -16,17 +20,16 @@ You should have received a copy of the GNU General Public License along with this program. If not, see . -''' -import sqlite3, os -from onionrutils import epoch -from utils import identifyhome, reconstructhash +""" + + class SentBox: def __init__(self): self.dbLocation = identifyhome.identify_home() + '/sentbox.db' if not os.path.exists(self.dbLocation): self.createDB() return - + def connect(self): self.conn = sqlite3.connect(self.dbLocation) self.cursor = self.conn.cursor() @@ -37,14 +40,14 @@ class SentBox: def createDB(self): conn = sqlite3.connect(self.dbLocation) cursor = conn.cursor() - cursor.execute('''CREATE TABLE sent( + cursor.execute("""CREATE TABLE sent( hash id not null, peer text not null, message text not null, subject text not null, date int not null ); - ''') + """) conn.commit() conn.close() return diff --git a/tests/test_onionrvalues.py b/tests/test_onionrvalues.py new file mode 100644 index 00000000..0bce9fe5 --- /dev/null +++ b/tests/test_onionrvalues.py @@ -0,0 +1,19 @@ +#!/usr/bin/env python3 +import sys, os +sys.path.append(".") +sys.path.append("src/") +import unittest, uuid +import json +TEST_DIR = 'testdata/%s-%s' % (uuid.uuid4(), os.path.basename(__file__)) + '/' +print("Test directory:", TEST_DIR) +os.environ["ONIONR_HOME"] = TEST_DIR + +from utils import identifyhome, createdirs +from etc import onionrvalues + +class TestOnionrValues(unittest.TestCase): + def test_default_expire(self): + self.assertEqual(onionrvalues.DEFAULT_EXPIRE, 2592000) + + +unittest.main() diff --git a/tests/test_template.py b/tests/test_template.py index 3e8cca75..7ab6cf6b 100644 --- a/tests/test_template.py +++ b/tests/test_template.py @@ -14,9 +14,6 @@ createdirs.create_dirs() setup_config() class TestTemplate(unittest.TestCase): - ''' - Tests both the onionrusers class and the contactmanager (which inherits it) - ''' def test_true(self): self.assertTrue(True) From 7f8aa64fa41c629d25a6c72982b64a379414d664 Mon Sep 17 00:00:00 2001 From: Kevin Froman Date: Wed, 29 Jan 2020 13:44:35 -0600 Subject: [PATCH 5/9] update onion in readme --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 589798b8..74f8541f 100644 --- a/README.md +++ b/README.md @@ -17,7 +17,7 @@ | | | | | ----------- | ----------- | ----------- | | [Install](#install-and-run-on-linux) | [Features](#main-features) | [Screenshots](#screenshots)| -| [Docs](#documentation)/[web copy](https://beardog108.github.io/onionr/) | [Get involved](#help-out) | [Onionr.net](https://onionr.net/)/[.onion](http://onionr.onionkvc5ibm37bmxwr56bdxcdnb6w3wm4bdghh5qo6f6za7gn7styid.onion/) | +| [Docs](#documentation)/[web copy](https://beardog108.github.io/onionr/) | [Get involved](#help-out) | [Onionr.net](https://onionr.net/)/[.onion](http://onionrbak72t5zhbzuey2fdkpczlvhowgcpqc6uoyrd3uxztzxwz5cyd.onion/) |
From cb108cb99023bb8561f338fe1cf31dae2d340660 Mon Sep 17 00:00:00 2001 From: Kevin Froman Date: Wed, 29 Jan 2020 14:21:12 -0600 Subject: [PATCH 6/9] use webpass in torstats --- static-data/www/shared/main/torstats.js | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/static-data/www/shared/main/torstats.js b/static-data/www/shared/main/torstats.js index 91f00162..af51f840 100644 --- a/static-data/www/shared/main/torstats.js +++ b/static-data/www/shared/main/torstats.js @@ -1,4 +1,8 @@ -var torSource = new EventSourcePolyfill("/torcircuits") +var torSource = new EventSourcePolyfill('/torcircuits', { + headers: { + "token": webpass + } + }) var displays = document.getElementsByClassName('torInfo') for (x = 0; x < displays.length; x++){ From d0291c2fb3743f4482d5a235ac03dbc6f16967a4 Mon Sep 17 00:00:00 2001 From: Kevin Froman Date: Wed, 29 Jan 2020 15:44:01 -0600 Subject: [PATCH 7/9] Added passphrase generator script --- scripts/README.md | 3 +++ scripts/passphrase-generator.py | 21 +++++++++++++++++++++ 2 files changed, 24 insertions(+) create mode 100644 scripts/README.md create mode 100755 scripts/passphrase-generator.py diff --git a/scripts/README.md b/scripts/README.md new file mode 100644 index 00000000..acdc9cb4 --- /dev/null +++ b/scripts/README.md @@ -0,0 +1,3 @@ +This directory contains useful scripts and utilities that don't make sense to include as official Onionr features. + +passphrase-generator.py: very simple utility to generate and print a strong passphrase to stdout. 256 bits of entropy by default. \ No newline at end of file diff --git a/scripts/passphrase-generator.py b/scripts/passphrase-generator.py new file mode 100755 index 00000000..d0d98dad --- /dev/null +++ b/scripts/passphrase-generator.py @@ -0,0 +1,21 @@ +#!/usr/bin/env python3 + +"""Generate a 16 word passphase with 256 bits of entropy. + +Specify true to reduce to 128 bits""" + + +import sys + +import niceware + +byte_count = 32 # 256 bits of entropy with niceware + +arg = False +try: + arg = sys.argv[1].lower() + if arg == 'true': + byte_count = 16 +except IndexError: pass + +print(' '.join(niceware.generate_passphrase(byte_count))) From 2bc14b5c63ae333082df9e8f4d6df4eb5061e333 Mon Sep 17 00:00:00 2001 From: Kevin Froman Date: Wed, 29 Jan 2020 15:44:44 -0600 Subject: [PATCH 8/9] add gzip to exec bigbrother for sites and fix passphase print in site creator --- src/bigbrother/ministry/ofexec.py | 2 ++ src/onionrcommands/sitecreator.py | 2 +- 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/src/bigbrother/ministry/ofexec.py b/src/bigbrother/ministry/ofexec.py index a9cbcccd..51b1b857 100644 --- a/src/bigbrother/ministry/ofexec.py +++ b/src/bigbrother/ministry/ofexec.py @@ -45,9 +45,11 @@ def block_exec(event, info): """Prevent arbitrary code execution in eval/exec and log it.""" # because libraries have stupid amounts of compile/exec/eval, # We have to use a whitelist where it can be tolerated + # Generally better than nothing, not a silver bullet whitelisted_code = [ 'netrc.py', 'shlex.py', + 'gzip.py', '', 'werkzeug/test.py', 'multiprocessing/popen_fork.py', diff --git a/src/onionrcommands/sitecreator.py b/src/onionrcommands/sitecreator.py index b743aaae..b0d14ed0 100644 --- a/src/onionrcommands/sitecreator.py +++ b/src/onionrcommands/sitecreator.py @@ -40,7 +40,7 @@ If you want to update your site later you must remember the passphrase.''', passphrase = getpass.getpass( 'Please enter a site passphrase of at least ' + - onionrvalues.PASSWORD_LENGTH + ' characters.') + str(onionrvalues.PASSWORD_LENGTH) + ' characters.') confirm = getpass.getpass('Confirm passphrase:') if passphrase != confirm: From cffe74d3101f296d6e52fa6c178134f7de88f2a9 Mon Sep 17 00:00:00 2001 From: Kevin Froman Date: Wed, 29 Jan 2020 15:54:26 -0600 Subject: [PATCH 9/9] Added documentation for onionr sites --- docs/usage/pages.md | 59 +++++++++++++++++++++++++++++++++++++ docs/usage/single-page.png | Bin 0 -> 19681 bytes docs/usage/site-opener.png | Bin 0 -> 7040 bytes 3 files changed, 59 insertions(+) create mode 100644 docs/usage/pages.md create mode 100644 docs/usage/single-page.png create mode 100644 docs/usage/site-opener.png diff --git a/docs/usage/pages.md b/docs/usage/pages.md new file mode 100644 index 00000000..16b44e0a --- /dev/null +++ b/docs/usage/pages.md @@ -0,0 +1,59 @@ +Onionr sites come in two forms: + +* Single-page sites, identified by the hash of a single page contained within a single Onionr block. + +* Multi-page sites, identified by a user ID. Contains directory archives of a full site. + + +# Metadata Awareness + +Before creating an Onionr site, one should be cautious of the metadata one could be leaking. For example, some HTML generators may insert author meta tags. Onionr does not filter out any web page data. + +# No JavaScript, no third-party resources + +Currently, in order to protect Onionr users, JavaScript is disabled within Onionr sites. JS will remain present in the HTML file, but be non functional. Additionally, third party resources outside of Onionr cannot be loaded. + + +# Creating multi page sites + +Multi page sites are the most useful, as they can contain an arbitrary amount of static files. + +To create a single page site, create a directory for your site and write standard HTML file(s) within them. CSS, images and other files can be placed in the directory as well. The home page should be name index.html and in the parent level directory. + +Then, create a strong passphrase for the site. If the site will be updated, be sure to write it down or remember it. A strong passphrase can be generated by running: + +`$ scripts/passphrase-generator.py` + +Sample output: lovesick blubberer haemoglobin... and so on. + +## Generating or updating the site: + +`$ ./onionr.sh addsite` + +All files in the current working directory will be added to the site. + +The command will prompt for a passphrase. + +After the site is generated, a user ID that identifies the site will be outputted. + +# Creating single page sites + +Single page sites are incredibly straight forward. + +Single page sites cannot be modified or updated, but are somewhat more secure due to having lower complexity. + +To create a single page site, write a standard HTML file. Inline or data-uri CSS can be included, as well as data-uri images. Data-URI generators can be found online. + +After creating the HTML file, run this command: + +`$ ./onionr.sh addhtml filename.html` + +![single page screenshot](single-page.png) + +# Viewing sites + +To view a site, open the Onionr web interface and paste the site hash or ID into the site opener box that looks like this: + +![site opener box screenshot](site-opener.png) + +Then, press open. diff --git a/docs/usage/single-page.png b/docs/usage/single-page.png new file mode 100644 index 0000000000000000000000000000000000000000..5aac0ac89cc465d8a23f3df89f971603faf62d8f GIT binary patch literal 19681 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 SEg7LAkmW^N;3dN6_J05k3 literal 0 HcmV?d00001 diff --git a/docs/usage/site-opener.png b/docs/usage/site-opener.png new file mode 100644 index 0000000000000000000000000000000000000000..23fec8409d573ac8c330270ea3f505cdb8fac5d6 GIT binary patch literal 7040 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 bE0*A1yBl#@I-47Aqy{{BpsfkNZxjA+C}djY literal 0 HcmV?d00001