From 80fabbccbf6adffcbd9ac8f63284c7a02c010652 Mon Sep 17 00:00:00 2001 From: Arinerron Date: Mon, 5 Feb 2018 11:02:36 -0800 Subject: [PATCH 01/15] Create an issue template --- ISSUE_TEMPLATE.md | 12 ++++++++++++ 1 file changed, 12 insertions(+) create mode 100644 ISSUE_TEMPLATE.md diff --git a/ISSUE_TEMPLATE.md b/ISSUE_TEMPLATE.md new file mode 100644 index 00000000..874b405d --- /dev/null +++ b/ISSUE_TEMPLATE.md @@ -0,0 +1,12 @@ +# Expected Behavior + +# Actual Behavior + +# Steps to Reproduce + +# Version Information +Onionr: +OS: +Python: +Tor: +I2P: From 494871290414f952120bdc5d566fb04140b85bd0 Mon Sep 17 00:00:00 2001 From: Kevin Froman Date: Wed, 7 Feb 2018 03:04:58 -0600 Subject: [PATCH 02/15] work on peer encryption --- .gitignore | 1 + onionr/api.py | 7 +++++-- onionr/core.py | 10 ++++++---- onionr/onionrcrypto.py | 10 ++++++++-- onionr/onionrutils.py | 11 +++++++++++ readme.md | 2 ++ 6 files changed, 33 insertions(+), 8 deletions(-) diff --git a/.gitignore b/.gitignore index 0d9c0eda..2a7d956a 100644 --- a/.gitignore +++ b/.gitignore @@ -6,3 +6,4 @@ onionr/*.pyc onionr/*.log onionr/data/hs/hostname onionr/data/* +onionr/gnupg/* diff --git a/onionr/api.py b/onionr/api.py index a5e75e28..efb420c6 100755 --- a/onionr/api.py +++ b/onionr/api.py @@ -23,7 +23,7 @@ from multiprocessing import Process import configparser, sys, random, threading, hmac, hashlib, base64, time, math, gnupg, os, logger from core import Core -import onionrutils +import onionrutils, onionrcrypto class API: ''' Main HTTP API (Flask) @@ -56,6 +56,7 @@ class API: self.debug = debug self._privateDelayTime = 3 self._core = Core() + self._crypto = onionrcrypto.OnionrCrypto(self._core) self._utils = onionrutils.OnionrUtils(self._core) app = flask.Flask(__name__) bindPort = int(self.config['CLIENT']['PORT']) @@ -131,7 +132,9 @@ class API: pass elif action == 'ping': resp = Response("pong!") - elif action == 'setHMAC': + elif action == 'getHMAC': + resp = Response(self._crypto.generateHMAC()) + elif action == 'getSymmetric': pass elif action == 'getDBHash': resp = Response(self._utils.getBlockDBHash()) diff --git a/onionr/core.py b/onionr/core.py index d7ac589c..93a1bc7c 100644 --- a/onionr/core.py +++ b/onionr/core.py @@ -18,11 +18,11 @@ along with this program. If not, see . ''' import sqlite3, os, sys, time, math, gnupg, base64, tarfile, getpass, simplecrypt, hashlib, nacl, logger -from Crypto.Cipher import AES -from Crypto import Random +#from Crypto.Cipher import AES +#from Crypto import Random import netcontroller -import onionrutils +import onionrutils, onionrcrypto if sys.version_info < (3, 6): try: @@ -41,7 +41,9 @@ class Core: self.ownPGPID = '' self.blockDB = 'data/blocks.db' self.blockDataLocation = 'data/blocks/' + self.gpgHome = './data/pgp/' self._utils = onionrutils.OnionrUtils(self) + self._crypto = onionrcrypto.OnionrCrypto(self) if not os.path.exists('data/'): os.mkdir('data/') @@ -59,7 +61,7 @@ class Core: Uses own PGP home folder in the data/ directory ''' - gpg = gnupg.GPG(homedir='./data/pgp/') + gpg = gnupg.GPG(homedir=self.gpgHome) input_data = gpg.gen_key_input(key_type="RSA", key_length=1024, name_real=myID, name_email='anon@onionr', testing=True) key = gpg.gen_key(input_data) logger.info("Generating PGP key, this will take some time..") diff --git a/onionr/onionrcrypto.py b/onionr/onionrcrypto.py index fed23889..ccfcee7b 100644 --- a/onionr/onionrcrypto.py +++ b/onionr/onionrcrypto.py @@ -17,10 +17,11 @@ You should have received a copy of the GNU General Public License along with this program. If not, see . ''' -import nacl +import nacl, gnupg class OnionrCrypto: - def __init__(self): + def __init__(self, coreInstance): + self._core = coreInstance return def symmetricPeerEncrypt(self, data, key): @@ -31,3 +32,8 @@ class OnionrCrypto: def rsaEncrypt(self, peer, data): return + + def verifyPGP(self, peer, signature): + '''Verify PGP signed data''' + gpg = gnupg.GPG(homedir=self._core.gpgHome) + \ No newline at end of file diff --git a/onionr/onionrutils.py b/onionr/onionrutils.py index ca575073..14ca52f1 100644 --- a/onionr/onionrutils.py +++ b/onionr/onionrutils.py @@ -153,6 +153,17 @@ class OnionrUtils: retVal = False return retVal + + def getPeerPGPFingerprint(self, peer): + ''' + Get peer's PGP fingerprint + ''' + retData = '' + gpg = gnupg.GPG(homedir=self._core.gpgHome) + for i in gpg.list_keys(): + if peer in i['uids'][0]: + retData = i['fingerprint'] + return retData def validateID(self, id): ''' diff --git a/readme.md b/readme.md index 730d1270..fea71132 100644 --- a/readme.md +++ b/readme.md @@ -13,6 +13,8 @@ Major work in progress. This software is in heavy development. If for some reason you want to get involved, get in touch first. +**Onionr API and functionality is subject to non-backwards compatible change during development** + ## Disclaimer The Tor Project, I2P developers, and anyone else do not own, create, or endorse this project, and are not otherwise involved. From a0dc95c291a56029f83a94b544ed042dc5558226 Mon Sep 17 00:00:00 2001 From: Kevin Froman Date: Thu, 8 Feb 2018 03:14:32 -0600 Subject: [PATCH 03/15] work on peer private messages (& crypto) --- onionr/communicator.py | 35 +---------------------------------- onionr/onionr.py | 13 +++++++++++++ onionr/onionrutils.py | 5 +++++ 3 files changed, 19 insertions(+), 34 deletions(-) diff --git a/onionr/communicator.py b/onionr/communicator.py index cd32d881..2e383fb2 100755 --- a/onionr/communicator.py +++ b/onionr/communicator.py @@ -69,40 +69,7 @@ class OnionrCommunicate: time.sleep(1) return - - def getRemotePeerKey(self, peerID): - ''' - This function contacts a peer and gets their main PGP key. - - This is safe because Tor or I2P is used, but it does not ensure that the person is who they say they are - ''' - url = 'http://' + peerID + '/public/?action=getPGP' - r = requests.get(url, headers=headers) - response = r.text - - return response - - def shareHMAC(self, peerID, key): - ''' - This function shares an HMAC key to a peer - ''' - - return - - def getPeerProof(self, peerID): - ''' - This function gets the current peer proof requirement - ''' - - return - - def sendPeerProof(self, peerID, data): - ''' - This function sends the proof result to a peer previously fetched with getPeerProof - ''' - - return - + def lookupBlocks(self): ''' Lookup blocks and merge new ones diff --git a/onionr/onionr.py b/onionr/onionr.py index 9c12b1b7..c4d360af 100755 --- a/onionr/onionr.py +++ b/onionr/onionr.py @@ -128,6 +128,7 @@ class Onionr: 'addmessage': self.addMessage, 'add-msg': self.addMessage, 'add-message': self.addMessage, + 'pm': self.sendEncrypt, 'gui': self.openGUI, 'addpeer': self.addPeer, 'add-peer': self.addPeer @@ -150,6 +151,18 @@ class Onionr: logger.info('Onionr ' + ONIONR_VERSION + ' (' + platform.machine() + ') : API v' + API_VERSION) logger.info('Running on ' + platform.platform() + ' ' + platform.release()) + def sendEncrypt(self): + '''Create a private message and send it''' + while True: + peer = logger.readline('Peer to send to: ') + if self.onionrUtils.validateID(peer): + break + else: + logger.error('Invalid peer ID') + message = logger.readline("Enter a message: ") + logger.info("Sending message to " + peer) + + def openGUI(self): gui.OnionrGUI(self.onionrCore) diff --git a/onionr/onionrutils.py b/onionr/onionrutils.py index 14ca52f1..619e5927 100644 --- a/onionr/onionrutils.py +++ b/onionr/onionrutils.py @@ -208,3 +208,8 @@ class OnionrUtils: retVal = False return retVal + + def sendPM(self, peer, message): + '''Send an encrypted private message to a user''' + + return From 297cac81ae4b7e9e2a1e662d5f229e97a9dae5aa Mon Sep 17 00:00:00 2001 From: Kevin Froman Date: Thu, 8 Feb 2018 16:58:39 -0600 Subject: [PATCH 04/15] work on peer encryption --- onionr/api.py | 4 ++-- onionr/onionr.py | 1 + onionr/onionrcrypto.py | 6 +++++- 3 files changed, 8 insertions(+), 3 deletions(-) diff --git a/onionr/api.py b/onionr/api.py index efb420c6..92ba30a1 100755 --- a/onionr/api.py +++ b/onionr/api.py @@ -133,9 +133,9 @@ class API: elif action == 'ping': resp = Response("pong!") elif action == 'getHMAC': - resp = Response(self._crypto.generateHMAC()) + resp = Response(self._crypto.generateSymmetric()) elif action == 'getSymmetric': - pass + resp = Response(self._crypto.generateSymmetric()) elif action == 'getDBHash': resp = Response(self._utils.getBlockDBHash()) elif action == 'getBlockHashes': diff --git a/onionr/onionr.py b/onionr/onionr.py index c4d360af..87b02442 100755 --- a/onionr/onionr.py +++ b/onionr/onionr.py @@ -161,6 +161,7 @@ class Onionr: logger.error('Invalid peer ID') message = logger.readline("Enter a message: ") logger.info("Sending message to " + peer) + self.onionrUtils.sendPM(peer, message) def openGUI(self): diff --git a/onionr/onionrcrypto.py b/onionr/onionrcrypto.py index ccfcee7b..79bd339e 100644 --- a/onionr/onionrcrypto.py +++ b/onionr/onionrcrypto.py @@ -36,4 +36,8 @@ class OnionrCrypto: def verifyPGP(self, peer, signature): '''Verify PGP signed data''' gpg = gnupg.GPG(homedir=self._core.gpgHome) - \ No newline at end of file + + def generateSymmetric(): + return + def generateHMAC(): + return \ No newline at end of file From 6f4ec049aeea51adffd0721e92d71d73f8916eb8 Mon Sep 17 00:00:00 2001 From: Kevin Froman Date: Sun, 11 Feb 2018 21:09:18 -0500 Subject: [PATCH 05/15] Update onionr-draft.md --- docs/onionr-draft.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/onionr-draft.md b/docs/onionr-draft.md index 6f0387b9..66880b1a 100644 --- a/docs/onionr-draft.md +++ b/docs/onionr-draft.md @@ -33,7 +33,7 @@ Clients MUST use HTTP(s) to communicate with one another to maintain compatibili ## Connections When a node first comes online, it attempts to bootstrap using a default list provided by a client. When two peers connect, they exchange PGP public keys and then generate a shared AES-SHA3-512 HMAC token. These keys are stored in a peer database until expiry. - HMAC tokens are regenerated either every X many communications with a peer or every X minutes. Every 10 communications or every 24 hours is a recommended default. + HMAC tokens are regenerated either every X many communications with a peer or every X minutes. Every 10MB or every 2 hours is a recommended default. All valid requests with HMAC should be recorded until used HMAC's expiry to prevent replay attacks. Peer Types * Friends: @@ -64,7 +64,7 @@ Clients MUST use HTTP(s) to communicate with one another to maintain compatibili ◦ Posts MUST be PGP signed, and MUST NOT use any encryption. ## Private Messages - Private messages are messages that can have attached images. They MUST be encrypted via AES256-HMAC-SHA256 and PGP signed (signed before encryption) and time stamped to prevent replaying. A temporary RSA key for use in every message is exchanged every X many configured messages (or posts), for use in addition with PGP and the HMAC. + Private messages are messages that can have attached images. They MUST be encrypted via AES256-HMAC-SHA256 and PGP signed (signed before encryption) and time stamped to prevent replaying. A temporary EdDSA key for use in every message is exchanged every X many configured messages (or posts), for use in addition with PGP and the HMAC. When both peers are online messages SHOULD be dispatched directly between peers. All messages must be verified prior to being displayed. From 7ea9be8821fc8b811bd3f9769c54a1af9f7fb42f Mon Sep 17 00:00:00 2001 From: Kevin Froman Date: Sun, 11 Feb 2018 21:09:38 -0500 Subject: [PATCH 06/15] Update onionr-draft.md --- docs/onionr-draft.md | 3 --- 1 file changed, 3 deletions(-) diff --git a/docs/onionr-draft.md b/docs/onionr-draft.md index 66880b1a..26fbf18f 100644 --- a/docs/onionr-draft.md +++ b/docs/onionr-draft.md @@ -4,9 +4,6 @@ A social network/microblogging platform for Tor & I2P Draft Dec 25 2017 -notes: -Use Blowfish in addition with AES? - # Overview Onionr is an encrypted microblogging & mailing system designed in the spirit of Twitter. From 895b1919fd2ffe6b617e3277d76496f76369671c Mon Sep 17 00:00:00 2001 From: Kevin Froman Date: Thu, 15 Feb 2018 23:31:30 -0500 Subject: [PATCH 07/15] removed PGP --- docs/onionr-draft.md | 75 +++++++++++++++--------------------------- onionr/api.py | 4 +-- onionr/communicator.py | 7 ---- onionr/core.py | 34 +++---------------- onionr/onionrcrypto.py | 10 ++---- onionr/onionrutils.py | 26 +-------------- onionr/tests.py | 27 --------------- requirements.txt | 8 ++--- 8 files changed, 36 insertions(+), 155 deletions(-) diff --git a/docs/onionr-draft.md b/docs/onionr-draft.md index 26fbf18f..6fb79cbe 100644 --- a/docs/onionr-draft.md +++ b/docs/onionr-draft.md @@ -1,72 +1,49 @@ -# Onionr Protocol Spec +# Onionr Protocol Spec v2 -A social network/microblogging platform for Tor & I2P - -Draft Dec 25 2017 +A P2P platform for Tor & I2P # Overview Onionr is an encrypted microblogging & mailing system designed in the spirit of Twitter. There are no central servers and all traffic is peer to peer by default (routed via Tor or I2P). -User IDs are simply Tor onion service/I2P host id + PGP fingerprint. -Clients consolidate feeds from peers into 1 “timeline” using RSS format. -Private messages are only accessible by the intended peer based on the PGP id. -Onionr is not intended to be a replacement for Ricochet, OnionShare, or Briar. -All traffic is over onion/I2P because if only some was, then that would make that traffic inherently suspicious. +User IDs are simply Tor onion service/I2P host id + Ed25519 key fingerprint. +Private blocks are only able to be read by the intended peer. +All traffic is over Tor/I2P, connecting only to Tor onion and I2P hidden services. + ## Goals: - • Selective sharing of information with friends & public + • Selective sharing of information • Secure & semi-anonymous direct messaging • Forward secrecy • Defense in depth - • Data should be secure for years to come, quantum safe (though not necessarily every “layer”) + • Data should be secure for years to come • Decentralization * Avoid browser-based exploits that plague similar software * Avoid timing attacks & unexpected metadata leaks -## Assumptions: - • Tor & I2P’s transport protocols & AES-256 are not broken, sha3-512 2nd preimage attacks will remain infeasible indefinitely - • All traffic is logged indefinitely by powerful adversaries + ## Protocol -Clients MUST use HTTP(s) to communicate with one another to maintain compatibility cross platform. HTTPS is recommended, but HTTP is acceptable because Tor & I2P provide transport layer security. + +Onionr nodes use HTTP (over Tor/I2P) to exchange keys, metadata, and blocks. Blocks are identified by their sha3_256 hash. Nodes sync a table of blocks hashes and attempt to download blocks they do not yet have from random peers. + ## Connections - When a node first comes online, it attempts to bootstrap using a default list provided by a client. - When two peers connect, they exchange PGP public keys and then generate a shared AES-SHA3-512 HMAC token. These keys are stored in a peer database until expiry. - HMAC tokens are regenerated either every X many communications with a peer or every X minutes. Every 10MB or every 2 hours is a recommended default. - All valid requests with HMAC should be recorded until used HMAC's expiry to prevent replay attacks. - Peer Types - * Friends: - * Encrypted ‘friends only’ posts to one another - * Usually less strict rate & storage limits - * OPTIONALLY sign one another’s keys. Users may not want to do this in order to avoid exposing their entire friends list. - • Strangers: - * Used for storage of encrypted or public information - * Can only read public posts - * Usually stricter rate & storage limits -## Data Storage/Delivery - Posts (public or friends only) are stored across the network. - Private messages SHOULD be delivered directly if both peers are online, otherwise stored in the network. - Data SHOULD be stored in an entirely encrypted state when a client is offline, including metadata. Data SHOULD be stored in a minimal size with garbage data to ensure some level of plausible deniablity. - Data SHOULD be stored as long as the node’s user prefers and only erased once disk quota is reached due to new data. - Posts - Posts can contain text and images. All posts MUST be time stamped. - Images SHOULD not be displayed by non-friends by default, to prevent unwanted viewing of offensive material & to reduce attack surface. - All received posts must be verified to be stored and/or displayed to the user. +When a node first comes online, it attempts to bootstrap using a default list provided by a client. +When two peers connect, they exchange Ed25519 keys (if applicable) then Salsa20 keys. - All data being transfered MUST be encrypted to the end node receiving the data, then the data MUST be encrypted the node(s) transporting/storing the data, +Salsa20 keys are regenerated either every X many communications with a peer or every X minutes. - Posts have two settings: - • Friends only: - ◦ Posts MUST be encrypted to all trusted peers via AES256-HMAC-SHA256 and PGP signed (signed before encryption) and time stamped to prevent replaying. A temporary RSA key for use in every post (or message) is exchanged every X many configured post (or message), for use in addition with PGP and the HMAC. - • Public: - ◦ Posts MUST be PGP signed, and MUST NOT use any encryption. -## Private Messages +Every 100kb or every 2 hours is a recommended default. - Private messages are messages that can have attached images. They MUST be encrypted via AES256-HMAC-SHA256 and PGP signed (signed before encryption) and time stamped to prevent replaying. A temporary EdDSA key for use in every message is exchanged every X many configured messages (or posts), for use in addition with PGP and the HMAC. - When both peers are online messages SHOULD be dispatched directly between peers. - All messages must be verified prior to being displayed. +All valid requests with HMAC should be recorded until used HMAC's expiry to prevent replay attacks. +Peer Types + * Friends: + * Encrypted ‘friends only’ posts to one another + * Usually less strict rate & storage limits + * Strangers: + * Used for storage of encrypted or public information + * Can only read public posts + * Usually stricter rate & storage limits - Clients SHOULD allow configurable message padding. ## Spam mitigation To send or receive data, a node can optionally request that the other node generate a hash that when in hexadecimal representation contains a random string at a random location in the string. Clients will configure what difficulty to request, and what difficulty is acceptable for themselves to perform. Difficulty should correlate with recent network & disk usage and data size. Friends can be configured to have less strict (to non existent) limits, separately from strangers. (proof of work). -Rate limits can be strict, as Onionr is not intended to be an instant messaging application. +Rate limits can be strict, as Onionr is not intended to be an instant messaging application. \ No newline at end of file diff --git a/onionr/api.py b/onionr/api.py index 92ba30a1..73c65b48 100755 --- a/onionr/api.py +++ b/onionr/api.py @@ -20,7 +20,7 @@ import flask from flask import request, Response, abort from multiprocessing import Process -import configparser, sys, random, threading, hmac, hashlib, base64, time, math, gnupg, os, logger +import configparser, sys, random, threading, hmac, hashlib, base64, time, math, os, logger from core import Core import onionrutils, onionrcrypto @@ -140,8 +140,6 @@ class API: resp = Response(self._utils.getBlockDBHash()) elif action == 'getBlockHashes': resp = Response(self._core.getBlockList()) - elif action == 'getPGP': - resp = Response(self._utils.exportMyPubkey()) # setData should be something the communicator initiates, not this api elif action == 'getData': resp = self._core.getData(data) diff --git a/onionr/communicator.py b/onionr/communicator.py index 2e383fb2..7fe5ecd1 100755 --- a/onionr/communicator.py +++ b/onionr/communicator.py @@ -40,13 +40,6 @@ class OnionrCommunicate: self.peerData = {} # Session data for peers (recent reachability, speed, etc) - # get our own PGP fingerprint - fingerprintFile = 'data/own-fingerprint.txt' - if not os.path.exists(fingerprintFile): - self._core.generateMainPGP(torID) - with open(fingerprintFile,'r') as f: - self.pgpOwnFingerprint = f.read() - logger.info('My PGP fingerprint is ' + logger.colors.underline + self.pgpOwnFingerprint + logger.colors.reset + logger.colors.fg.green + '.') if os.path.exists(self._core.queueDB): self._core.clearDaemonQueue() while True: diff --git a/onionr/core.py b/onionr/core.py index 93a1bc7c..d1a03b31 100644 --- a/onionr/core.py +++ b/onionr/core.py @@ -17,7 +17,7 @@ You should have received a copy of the GNU General Public License along with this program. If not, see . ''' -import sqlite3, os, sys, time, math, gnupg, base64, tarfile, getpass, simplecrypt, hashlib, nacl, logger +import sqlite3, os, sys, time, math, base64, tarfile, getpass, simplecrypt, hashlib, nacl, logger #from Crypto.Cipher import AES #from Crypto import Random import netcontroller @@ -38,10 +38,8 @@ class Core: ''' self.queueDB = 'data/queue.db' self.peerDB = 'data/peers.db' - self.ownPGPID = '' self.blockDB = 'data/blocks.db' self.blockDataLocation = 'data/blocks/' - self.gpgHome = './data/pgp/' self._utils = onionrutils.OnionrUtils(self) self._crypto = onionrcrypto.OnionrCrypto(self) @@ -55,28 +53,6 @@ class Core: return - def generateMainPGP(self, myID): - ''' - Generate the main PGP key for our client. Should not be done often. - - Uses own PGP home folder in the data/ directory - ''' - gpg = gnupg.GPG(homedir=self.gpgHome) - input_data = gpg.gen_key_input(key_type="RSA", key_length=1024, name_real=myID, name_email='anon@onionr', testing=True) - key = gpg.gen_key(input_data) - logger.info("Generating PGP key, this will take some time..") - while key.status != "key created": - time.sleep(0.5) - print(key.status) - - logger.info("Finished generating PGP key") - # Write the key - myFingerpintFile = open('data/own-fingerprint.txt', 'w') - myFingerpintFile.write(key.fingerprint) - myFingerpintFile.close() - - return - def addPeer(self, peerID, name=''): ''' Add a peer by their ID, with an optional name, to the peer database @@ -104,8 +80,7 @@ class Core: c.execute('''CREATE TABLE peers( ID text not null, name text, - pgpKey text, - hmacKey text, + pubkey text, blockDBHash text, forwardKey text, dateSeen not null, @@ -335,7 +310,6 @@ class Core: id text 0 name text, 1 - pgpKey text, 2 hmacKey text, 3 blockDBHash text, 4 forwardKey text, 5 @@ -346,7 +320,7 @@ class Core: conn = sqlite3.connect(self.peerDB) c = conn.cursor() command = (peer,) - infoNumbers = {'id': 0, 'name': 1, 'pgpKey': 2, 'hmacKey': 3, 'blockDBHash': 4, 'forwardKey': 5, 'dateSeen': 6, 'bytesStored': 7, 'trust': 8} + infoNumbers = {'id': 0, 'name': 1, 'hmacKey': 3, 'blockDBHash': 4, 'forwardKey': 5, 'dateSeen': 6, 'bytesStored': 7, 'trust': 8} info = infoNumbers[info] iterCount = 0 retVal = '' @@ -369,7 +343,7 @@ class Core: c = conn.cursor() command = (data, peer) # TODO: validate key on whitelist - if key not in ('id', 'text', 'name', 'pgpKey', 'hmacKey', 'blockDBHash', 'forwardKey', 'dateSeen', 'bytesStored', 'trust'): + if key not in ('id', 'name', 'pubkey', 'blockDBHash', 'forwardKey', 'dateSeen', 'bytesStored', 'trust'): raise Exception("Got invalid database key when setting peer info") c.execute('UPDATE peers SET ' + key + ' = ? WHERE id=?', command) conn.commit() diff --git a/onionr/onionrcrypto.py b/onionr/onionrcrypto.py index 79bd339e..e9f89c7c 100644 --- a/onionr/onionrcrypto.py +++ b/onionr/onionrcrypto.py @@ -17,7 +17,7 @@ You should have received a copy of the GNU General Public License along with this program. If not, see . ''' -import nacl, gnupg +import nacl class OnionrCrypto: def __init__(self, coreInstance): @@ -29,15 +29,9 @@ class OnionrCrypto: def symmetricPeerDecrypt(self, data, key): return - - def rsaEncrypt(self, peer, data): - return - - def verifyPGP(self, peer, signature): - '''Verify PGP signed data''' - gpg = gnupg.GPG(homedir=self._core.gpgHome) def generateSymmetric(): return + def generateHMAC(): return \ No newline at end of file diff --git a/onionr/onionrutils.py b/onionr/onionrutils.py index 619e5927..ba082b98 100644 --- a/onionr/onionrutils.py +++ b/onionr/onionrutils.py @@ -18,7 +18,7 @@ along with this program. If not, see . ''' # Misc functions that do not fit in the main api, but are useful -import getpass, sys, requests, configparser, os, socket, gnupg, hashlib, logger, sqlite3 +import getpass, sys, requests, configparser, os, socket, hashlib, logger, sqlite3 if sys.version_info < (3, 6): try: import sha3 @@ -93,19 +93,6 @@ class OnionrUtils: else: return True - def exportMyPubkey(self): - ''' - Export our PGP key if it exists - ''' - if not os.path.exists(self.fingerprintFile): - raise Exception("No fingerprint found, cannot export our PGP key.") - gpg = gnupg.GPG(homedir='./data/pgp/') - with open(self.fingerprintFile,'r') as f: - fingerprint = f.read() - ascii_armored_public_keys = gpg.export_keys(fingerprint) - - return ascii_armored_public_keys - def getBlockDBHash(self): ''' Return a sha3_256 hash of the blocks DB @@ -153,17 +140,6 @@ class OnionrUtils: retVal = False return retVal - - def getPeerPGPFingerprint(self, peer): - ''' - Get peer's PGP fingerprint - ''' - retData = '' - gpg = gnupg.GPG(homedir=self._core.gpgHome) - for i in gpg.list_keys(): - if peer in i['uids'][0]: - retData = i['fingerprint'] - return retData def validateID(self, id): ''' diff --git a/onionr/tests.py b/onionr/tests.py index 8babbdfd..557e8885 100755 --- a/onionr/tests.py +++ b/onionr/tests.py @@ -85,33 +85,6 @@ class OnionrTests(unittest.TestCase): else: self.assertTrue(False) - def testPGPGen(self): - logger.debug('--------------------------') - logger.info('Running PGP key generation test...') - if os.path.exists('data/pgp/'): - self.assertTrue(True) - else: - import core, netcontroller - myCore = core.Core() - net = netcontroller.NetController(1337) - net.startTor() - torID = open('data/hs/hostname').read() - myCore.generateMainPGP(torID) - if os.path.exists('data/pgp/'): - self.assertTrue(True) - - def testHMACGen(self): - logger.debug('--------------------------') - logger.info('Running HMAC generation test...') - # Test if hmac key generation is working - import core - myCore = core.Core() - key = myCore.generateHMAC() - if len(key) > 10: - self.assertTrue(True) - else: - self.assertTrue(False) - def testQueue(self): logger.debug('--------------------------') logger.info('Running daemon queue test...') diff --git a/requirements.txt b/requirements.txt index da4c8869..7f937ff4 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,10 +1,6 @@ PyNaCl==1.2.1 -gnupg==2.3.1 +requests==2.12.4 Flask==0.12.2 -requests==2.18.4 -urllib3==1.22 simple_crypt==4.1.7 +urllib3==1.19.1 sha3==0.2.1 -pycrypto==2.6.1 -pynacl==1.2.1 -PySocks==1.6.8 From 38aa8620ae192e4c0622d797a849b9d9dd60d0b0 Mon Sep 17 00:00:00 2001 From: Kevin Froman Date: Thu, 15 Feb 2018 23:39:34 -0500 Subject: [PATCH 08/15] removed PGP --- requirements.txt | 1 + 1 file changed, 1 insertion(+) diff --git a/requirements.txt b/requirements.txt index 7f937ff4..ea4566e9 100644 --- a/requirements.txt +++ b/requirements.txt @@ -4,3 +4,4 @@ Flask==0.12.2 simple_crypt==4.1.7 urllib3==1.19.1 sha3==0.2.1 +PySocks=1.6.8 From 15e0102be071861dbd77879ce0b9879629144269 Mon Sep 17 00:00:00 2001 From: Kevin Froman Date: Thu, 15 Feb 2018 23:41:54 -0500 Subject: [PATCH 09/15] removed PGP --- requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements.txt b/requirements.txt index ea4566e9..77e25537 100644 --- a/requirements.txt +++ b/requirements.txt @@ -4,4 +4,4 @@ Flask==0.12.2 simple_crypt==4.1.7 urllib3==1.19.1 sha3==0.2.1 -PySocks=1.6.8 +PySocks==1.6.8 From 586e9230cd61b3985c5bc60320eac87a98b51e9f Mon Sep 17 00:00:00 2001 From: Kevin Froman Date: Tue, 20 Feb 2018 20:44:56 -0600 Subject: [PATCH 10/15] fixed message spam, rewrote draft, work on crypto --- docs/onionr-draft.md | 2 ++ onionr/api.py | 8 ++++---- onionr/communicator.py | 3 ++- onionr/core.py | 3 ++- onionr/netcontroller.py | 9 +++++++-- onionr/onionr.py | 4 +++- onionr/onionrcrypto.py | 42 +++++++++++++++++++++++++++++++++++------ reset.sh | 5 +++++ 8 files changed, 61 insertions(+), 15 deletions(-) create mode 100755 reset.sh diff --git a/docs/onionr-draft.md b/docs/onionr-draft.md index 6fb79cbe..5ab91cb0 100644 --- a/docs/onionr-draft.md +++ b/docs/onionr-draft.md @@ -24,6 +24,8 @@ All traffic is over Tor/I2P, connecting only to Tor onion and I2P hidden service Onionr nodes use HTTP (over Tor/I2P) to exchange keys, metadata, and blocks. Blocks are identified by their sha3_256 hash. Nodes sync a table of blocks hashes and attempt to download blocks they do not yet have from random peers. +Blocks may be encrypted using Curve25519. + ## Connections When a node first comes online, it attempts to bootstrap using a default list provided by a client. diff --git a/onionr/api.py b/onionr/api.py index 73c65b48..666b7783 100755 --- a/onionr/api.py +++ b/onionr/api.py @@ -47,7 +47,7 @@ class API: if os.path.exists('dev-enabled'): self._developmentMode = True logger.set_level(logger.LEVEL_DEBUG) - logger.warn('DEVELOPMENT MODE ENABLED (THIS IS LESS SECURE!)') + #logger.warn('DEVELOPMENT MODE ENABLED (THIS IS LESS SECURE!)') else: self._developmentMode = False logger.set_level(logger.LEVEL_INFO) @@ -172,9 +172,9 @@ class API: resp = Response("Invalid request") return resp - - logger.info('Starting client on ' + self.host + ':' + str(bindPort) + '...') - logger.debug('Client token: ' + logger.colors.underline + self.clientToken) + if not os.environ.get("WERKZEUG_RUN_MAIN") == "true": + logger.info('Starting client on ' + self.host + ':' + str(bindPort) + '...') + logger.debug('Client token: ' + logger.colors.underline + self.clientToken) app.run(host=self.host, port=bindPort, debug=True, threaded=True) diff --git a/onionr/communicator.py b/onionr/communicator.py index 7fe5ecd1..4b6e49a8 100755 --- a/onionr/communicator.py +++ b/onionr/communicator.py @@ -20,7 +20,7 @@ and code to operate as a daemon, getting commands from the command queue databas along with this program. If not, see . ''' import sqlite3, requests, hmac, hashlib, time, sys, os, math, logger, urllib.parse -import core, onionrutils +import core, onionrutils, onionrcrypto class OnionrCommunicate: def __init__(self, debug, developmentMode): @@ -31,6 +31,7 @@ class OnionrCommunicate: ''' self._core = core.Core() self._utils = onionrutils.OnionrUtils(self._core) + self._crypto = onionrcrypto.OnionrCrypto(self._core) blockProcessTimer = 0 blockProcessAmount = 5 heartBeatTimer = 0 diff --git a/onionr/core.py b/onionr/core.py index d1a03b31..29d84a55 100644 --- a/onionr/core.py +++ b/onionr/core.py @@ -41,13 +41,14 @@ class Core: self.blockDB = 'data/blocks.db' self.blockDataLocation = 'data/blocks/' self._utils = onionrutils.OnionrUtils(self) + + # Initialize the crypto object self._crypto = onionrcrypto.OnionrCrypto(self) if not os.path.exists('data/'): os.mkdir('data/') if not os.path.exists('data/blocks/'): os.mkdir('data/blocks/') - if not os.path.exists(self.blockDB): self.createBlockDB() diff --git a/onionr/netcontroller.py b/onionr/netcontroller.py index dcb3c3a5..058d8e4a 100644 --- a/onionr/netcontroller.py +++ b/onionr/netcontroller.py @@ -103,7 +103,12 @@ HiddenServicePort 80 127.0.0.1:''' + str(self.hsPort) + ''' int(pidN) except: return - os.kill(int(pidN), signal.SIGTERM) - os.remove('data/torPid.txt') + try: + os.kill(int(pidN), signal.SIGTERM) + os.remove('data/torPid.txt') + except ProcessLookupError: + pass + except FileNotFoundError: + pass return diff --git a/onionr/onionr.py b/onionr/onionr.py index 87b02442..d3c51a4f 100755 --- a/onionr/onionr.py +++ b/onionr/onionr.py @@ -47,7 +47,6 @@ class Onionr: if os.path.exists('dev-enabled'): self._developmentMode = True logger.set_level(logger.LEVEL_DEBUG) - logger.warn('DEVELOPMENT MODE ENABLED (THIS IS LESS SECURE!)') else: self._developmentMode = False logger.set_level(logger.LEVEL_INFO) @@ -211,11 +210,14 @@ class Onionr: def daemon(self): ''' Start the Onionr communication daemon ''' if not os.environ.get("WERKZEUG_RUN_MAIN") == "true": + if self._developmentMode: + logger.warn('DEVELOPMENT MODE ENABLED (THIS IS LESS SECURE!)') net = NetController(self.config['CLIENT']['PORT']) logger.info('Tor is starting...') if not net.startTor(): sys.exit(1) logger.info('Started Tor .onion service: ' + logger.colors.underline + net.myID) + logger.info('Our Public key: ' + self.onionrCore._crypto.pubKey) time.sleep(1) subprocess.Popen(["./communicator.py", "run", str(net.socksPort)]) logger.debug('Started communicator') diff --git a/onionr/onionrcrypto.py b/onionr/onionrcrypto.py index e9f89c7c..6b282341 100644 --- a/onionr/onionrcrypto.py +++ b/onionr/onionrcrypto.py @@ -17,21 +17,51 @@ You should have received a copy of the GNU General Public License along with this program. If not, see . ''' -import nacl +import nacl.signing, nacl.encoding, nacl.public, os class OnionrCrypto: def __init__(self, coreInstance): self._core = coreInstance + self._keyFile = 'data/keys.txt' + self.pubKey = None + self.privKey = None + + # Load our own pub/priv Ed25519 keys, gen & save them if they don't exist + if os.path.exists(self._keyFile): + with open('data/keys.txt', 'r') as keys: + keys = keys.read().split(',') + self.pubKey = keys[0] + self.privKey = keys[1] + else: + keys = self.generatePubKey() + self.pubKey = keys[0] + self.privKey = keys[1] + with open(self._keyFile, 'w') as keyfile: + keyfile.write(self.pubKey + ',' + self.privKey) return - def symmetricPeerEncrypt(self, data, key): + def pubKeyEncrypt(self, data, peer): + '''Encrypt to a peers public key (Curve25519, taken from Ed25519 pubkey)''' return - def symmetricPeerDecrypt(self, data, key): + def pubKeyEncrypt(self, data, peer): + '''pubkey decrypt (Curve25519, taken from Ed25519 pubkey)''' + return + + def symmetricPeerEncrypt(self, data): + '''Salsa20 encrypt data to peer (with mac)''' + return + + def symmetricPeerDecrypt(self, data, peer): + '''Salsa20 decrypt data from peer (with mac)''' return - def generateSymmetric(): + def generateSymmetric(self, data, peer): + '''Generate symmetric key''' return - def generateHMAC(): - return \ No newline at end of file + def generatePubKey(self): + '''Generate a Ed25519 public key pair, return tuple of base64encoded pubkey, privkey''' + private_key = nacl.signing.SigningKey.generate() + public_key = private_key.verify_key.encode(encoder=nacl.encoding.Base32Encoder()) + return (public_key.decode(), private_key.encode(encoder=nacl.encoding.Base32Encoder()).decode()) \ No newline at end of file diff --git a/reset.sh b/reset.sh new file mode 100755 index 00000000..eaf6641b --- /dev/null +++ b/reset.sh @@ -0,0 +1,5 @@ +#!/bin/bash +echo "RESETING ONIONR" +rm onionr/data/blocks/*.dat +rm onionr/data/peers.db +rm onionr/data/blocks.db From 6beff53e458a91fe3f7cd55fe04a6b1eb4904e6d Mon Sep 17 00:00:00 2001 From: Kevin Froman Date: Tue, 20 Feb 2018 21:17:24 -0600 Subject: [PATCH 11/15] updated readme --- readme.md | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/readme.md b/readme.md index fea71132..f143aec2 100644 --- a/readme.md +++ b/readme.md @@ -2,13 +2,21 @@ [![Build Status](https://travis-ci.org/beardog108/onionr.svg?branch=master)](https://travis-ci.org/beardog108/onionr) -P2P microblogging platform and social network, using Tor & I2P. +P2P platform, using Tor & I2P. Major work in progress. ***THIS SOFTWARE IS NOT USABLE OR SAFE YET.*** +**Roadmap/features:** + +* [X] Fully p2p/decentralized, no trackers or other single points of failure +* [X] High level of anonymity +* [] End to end encryption where applicable +* [X] Optional non-encrypted blocks, useful for blog posts or public file sharing +* [] Easy API system for integration to websites + # Development This software is in heavy development. If for some reason you want to get involved, get in touch first. From 916cb1f8acb62057cddbb764beb0e2c35dbdb778 Mon Sep 17 00:00:00 2001 From: Kevin Froman Date: Tue, 20 Feb 2018 21:19:27 -0600 Subject: [PATCH 12/15] updated readme --- readme.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/readme.md b/readme.md index f143aec2..c90492e9 100644 --- a/readme.md +++ b/readme.md @@ -13,9 +13,9 @@ Major work in progress. * [X] Fully p2p/decentralized, no trackers or other single points of failure * [X] High level of anonymity -* [] End to end encryption where applicable +* [ ] End to end encryption where applicable * [X] Optional non-encrypted blocks, useful for blog posts or public file sharing -* [] Easy API system for integration to websites +* [ ] Easy API system for integration to websites # Development From 38bfee5344f48532aa2e112a899b347b7be72c83 Mon Sep 17 00:00:00 2001 From: Kevin Froman Date: Wed, 21 Feb 2018 03:32:31 -0600 Subject: [PATCH 13/15] work on seperating pubkey from tor/i2p --- onionr/core.py | 53 +++++++++++++++++++++++++++---------------- onionr/onionrutils.py | 20 ++++++++++------ onionr/tests.py | 2 +- 3 files changed, 48 insertions(+), 27 deletions(-) diff --git a/onionr/core.py b/onionr/core.py index 29d84a55..8e72520d 100644 --- a/onionr/core.py +++ b/onionr/core.py @@ -40,6 +40,7 @@ class Core: self.peerDB = 'data/peers.db' self.blockDB = 'data/blocks.db' self.blockDataLocation = 'data/blocks/' + self.addressDB = 'data/address.db' self._utils = onionrutils.OnionrUtils(self) # Initialize the crypto object @@ -61,7 +62,7 @@ class Core: DOES NO SAFETY CHECKS if the ID is valid, but prepares the insertion ''' # This function simply adds a peer to the DB - if not self._utils.validateID(peerID): + if not self._utils.validatePubKey(peerID): return False conn = sqlite3.connect(self.peerDB) c = conn.cursor() @@ -70,6 +71,29 @@ class Core: conn.commit() conn.close() return True + + def createAddressDB(self): + ''' + Generate the address database + + types: + 1: I2P b32 address + 2: Tor v2 (like facebookcorewwwi.onion) + 3: Tor v3 + ''' + conn = sqlite3.connect(self.addressDB) + c = conn.cursor() + c.execute('''CREATE TABLE adders( + address text, + type int, + knownPeer text, + speed int, + success int, + failure int + ); + ''') + conn.commit() + conn.close() def createPeerDB(self): ''' @@ -81,7 +105,7 @@ class Core: c.execute('''CREATE TABLE peers( ID text not null, name text, - pubkey text, + adders text, blockDBHash text, forwardKey text, dateSeen not null, @@ -90,7 +114,6 @@ class Core: ''') conn.commit() conn.close() - return def createBlockDB(self): @@ -278,14 +301,6 @@ class Core: return - def generateHMAC(self, length=32): - ''' - Generate and return an HMAC key - ''' - key = base64.b64encode(os.urandom(length)) - - return key - def listPeers(self, randomOrder=True): ''' Return a list of peers @@ -300,7 +315,7 @@ class Core: peers = c.execute('SELECT * FROM peers;') peerList = [] for i in peers: - peerList.append(i[0]) + peerList.append(i[2]) conn.close() return peerList @@ -311,17 +326,17 @@ class Core: id text 0 name text, 1 - hmacKey text, 3 - blockDBHash text, 4 - forwardKey text, 5 - dateSeen not null, 7 - bytesStored int, 8 - trust int 9 + adders text, 2 + blockDBHash text, 3 + forwardKey text, 4 + dateSeen not null, 5 + bytesStored int, 6 + trust int 7 ''' conn = sqlite3.connect(self.peerDB) c = conn.cursor() command = (peer,) - infoNumbers = {'id': 0, 'name': 1, 'hmacKey': 3, 'blockDBHash': 4, 'forwardKey': 5, 'dateSeen': 6, 'bytesStored': 7, 'trust': 8} + infoNumbers = {'id': 0, 'name': 1, 'adders': 2, 'blockDBHash': 3, 'forwardKey': 4, 'dateSeen': 5, 'bytesStored': 6, 'trust': 7} info = infoNumbers[info] iterCount = 0 retVal = '' diff --git a/onionr/onionrutils.py b/onionr/onionrutils.py index ba082b98..ab57a288 100644 --- a/onionr/onionrutils.py +++ b/onionr/onionrutils.py @@ -19,6 +19,7 @@ ''' # Misc functions that do not fit in the main api, but are useful import getpass, sys, requests, configparser, os, socket, hashlib, logger, sqlite3 +import nacl.signing, nacl.encoding if sys.version_info < (3, 6): try: import sha3 @@ -140,10 +141,20 @@ class OnionrUtils: retVal = False return retVal + + def validatePubKey(self, key): + '''Validate if a string is a valid base32 encoded Ed25519 key''' + retVal = False + try: + nacl.signing.SigningKey(self, seed=key, encoder=nacl.encoding.Base32Encoder) + except nacl.exceptions.ValueError: + pass + return retVal + def validateID(self, id): ''' - Validate if a user ID is a valid tor or i2p hidden service + Validate if an address is a valid tor or i2p hidden service ''' idLength = len(id) retVal = True @@ -183,9 +194,4 @@ class OnionrUtils: if not idNoDomain.isalnum(): retVal = False - return retVal - - def sendPM(self, peer, message): - '''Send an encrypted private message to a user''' - - return + return retVal \ No newline at end of file diff --git a/onionr/tests.py b/onionr/tests.py index 557e8885..5728055c 100755 --- a/onionr/tests.py +++ b/onionr/tests.py @@ -54,7 +54,7 @@ class OnionrTests(unittest.TestCase): myCore = core.Core() if not os.path.exists('data/peers.db'): myCore.createPeerDB() - if myCore.addPeer('2ks5c5bm6zk3ejqg.onion') and not myCore.addPeer('invalidpeer.onion'): + if myCore.addPeer('6M5MXL237OK57ITHVYN5WGHANPGOMKS5C3PJLHBBNKFFJQOIDOJA====') and not myCore.addPeer('NFXHMYLMNFSAU==='): self.assertTrue(True) else: self.assertTrue(False) From 882f2e7020fe8c31e3795cac750f0cbef708a1c6 Mon Sep 17 00:00:00 2001 From: Kevin Froman Date: Thu, 22 Feb 2018 00:02:29 -0600 Subject: [PATCH 14/15] fixed crypto object created before directories are generated --- onionr/core.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/onionr/core.py b/onionr/core.py index 8e72520d..815bfd66 100644 --- a/onionr/core.py +++ b/onionr/core.py @@ -41,10 +41,6 @@ class Core: self.blockDB = 'data/blocks.db' self.blockDataLocation = 'data/blocks/' self.addressDB = 'data/address.db' - self._utils = onionrutils.OnionrUtils(self) - - # Initialize the crypto object - self._crypto = onionrcrypto.OnionrCrypto(self) if not os.path.exists('data/'): os.mkdir('data/') @@ -52,6 +48,10 @@ class Core: os.mkdir('data/blocks/') if not os.path.exists(self.blockDB): self.createBlockDB() + + self._utils = onionrutils.OnionrUtils(self) + # Initialize the crypto object + self._crypto = onionrcrypto.OnionrCrypto(self) return From 15400432b1c548aa517b68cb1429eb2319034c04 Mon Sep 17 00:00:00 2001 From: Kevin Froman Date: Thu, 22 Feb 2018 00:08:04 -0600 Subject: [PATCH 15/15] fixed pubkey validation not working --- onionr/onionrutils.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/onionr/onionrutils.py b/onionr/onionrutils.py index ab57a288..b000e799 100644 --- a/onionr/onionrutils.py +++ b/onionr/onionrutils.py @@ -146,9 +146,11 @@ class OnionrUtils: '''Validate if a string is a valid base32 encoded Ed25519 key''' retVal = False try: - nacl.signing.SigningKey(self, seed=key, encoder=nacl.encoding.Base32Encoder) + nacl.signing.SigningKey(seed=key, encoder=nacl.encoding.Base32Encoder) except nacl.exceptions.ValueError: pass + else: + retVal = True return retVal