bug fixes and refactoring
parent
95750b6b3c
commit
a4d6dc5fa5
10
README.md
10
README.md
|
@ -5,7 +5,7 @@
|
||||||
</p>
|
</p>
|
||||||
|
|
||||||
<p align="center">
|
<p align="center">
|
||||||
Anonymous P2P storage network 🕵️
|
Anonymous P2P communication network 🕵️
|
||||||
</p>
|
</p>
|
||||||
|
|
||||||
(***pre-alpha & experimental, not well tested or easy to use yet***)
|
(***pre-alpha & experimental, not well tested or easy to use yet***)
|
||||||
|
@ -19,7 +19,7 @@
|
||||||
|
|
||||||
# About
|
# About
|
||||||
|
|
||||||
Onionr is a decentralized, peer-to-peer data storage network, designed to be anonymous and resistant to (meta)data analysis and spam/disruption.
|
Onionr is a decentralized, peer-to-peer communication and storage network, designed to be anonymous and resistant to (meta)data analysis and spam/disruption.
|
||||||
|
|
||||||
Onionr stores data in independent packages referred to as 'blocks'. The blocks are synced to all other nodes in the network. 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.
|
Onionr stores data in independent packages referred to as 'blocks'. The blocks are synced to all other nodes in the network. 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.
|
||||||
|
|
||||||
|
@ -29,7 +29,7 @@ Onionr can be used for mail, as a social network, instant messenger, file sharin
|
||||||
|
|
||||||
The whitepaper (subject to change prior to first alpha release) is available [here](docs/whitepaper.md).
|
The whitepaper (subject to change prior to first alpha release) is available [here](docs/whitepaper.md).
|
||||||
|
|
||||||
![Tor stinks slide image](docs/tor-stinks-02.png)
|
![node web illustration](docs/onionr-web.png)
|
||||||
|
|
||||||
## Main Features
|
## Main Features
|
||||||
|
|
||||||
|
@ -54,7 +54,7 @@ Friend/contact manager
|
||||||
|
|
||||||
<img alt='Encrypted, metadata-masking mail application screenshot' src='docs/onionr-3.png' width=600>
|
<img alt='Encrypted, metadata-masking mail application screenshot' src='docs/onionr-3.png' width=600>
|
||||||
|
|
||||||
Encrypted, metadata-masking mail application. Perhaps the first distributed mail system to have basic forward secrecy.
|
Encrypted, metadata-masking mail application. One of the first distributed mail systems to have basic forward secrecy.
|
||||||
|
|
||||||
# Documentation
|
# Documentation
|
||||||
|
|
||||||
|
@ -116,3 +116,5 @@ The 'open source badge' is by Maik Ellerbrock and is licensed under a Creative C
|
||||||
The Onionr logo was created by [Anhar Ismail](https://github.com/anharismail) under the [Creative Commons Attribution 4.0 International License](https://creativecommons.org/licenses/by/4.0/).
|
The Onionr logo was created by [Anhar Ismail](https://github.com/anharismail) under the [Creative Commons Attribution 4.0 International License](https://creativecommons.org/licenses/by/4.0/).
|
||||||
|
|
||||||
If you modify and redistribute our code ("forking"), please use a different logo and project name to avoid confusion. Please do not use our logo in a way that makes it seem like we endorse you without permission.
|
If you modify and redistribute our code ("forking"), please use a different logo and project name to avoid confusion. Please do not use our logo in a way that makes it seem like we endorse you without permission.
|
||||||
|
|
||||||
|
![Tor stinks slide image](docs/tor-stinks-02.png)
|
||||||
|
|
|
@ -26,7 +26,7 @@ import core
|
||||||
from onionrblockapi import Block
|
from onionrblockapi import Block
|
||||||
import onionrutils, onionrexceptions, onionrcrypto, blockimporter, onionrevents as events, logger, config
|
import onionrutils, onionrexceptions, onionrcrypto, blockimporter, onionrevents as events, logger, config
|
||||||
import httpapi
|
import httpapi
|
||||||
from httpapi import friendsapi, simplecache, profilesapi, configapi
|
from httpapi import friendsapi, simplecache, profilesapi, configapi, miscpublicapi
|
||||||
from onionrservices import httpheaders
|
from onionrservices import httpheaders
|
||||||
import onionr
|
import onionr
|
||||||
|
|
||||||
|
@ -111,36 +111,12 @@ class PublicAPI:
|
||||||
|
|
||||||
@app.route('/getblocklist')
|
@app.route('/getblocklist')
|
||||||
def getBlockList():
|
def getBlockList():
|
||||||
# Provide a list of our blocks, with a date offset
|
return httpapi.miscpublicapi.public_block_list(clientAPI, self, request)
|
||||||
dateAdjust = request.args.get('date')
|
|
||||||
bList = clientAPI._core.getBlockList(dateRec=dateAdjust)
|
|
||||||
if config.get('general.hide_created_blocks', True):
|
|
||||||
for b in self.hideBlocks:
|
|
||||||
if b in bList:
|
|
||||||
# Don't share blocks we created if they haven't been *uploaded* yet, makes it harder to find who created a block
|
|
||||||
bList.remove(b)
|
|
||||||
return Response('\n'.join(bList))
|
|
||||||
|
|
||||||
@app.route('/getdata/<name>')
|
@app.route('/getdata/<name>')
|
||||||
def getBlockData(name):
|
def getBlockData(name):
|
||||||
# Share data for a block if we have it
|
# Share data for a block if we have it
|
||||||
resp = ''
|
return httpapi.miscpublicapi.public_get_block_data(clientAPI, self, name)
|
||||||
data = name
|
|
||||||
if clientAPI._utils.validateHash(data):
|
|
||||||
if not config.get('general.hide_created_blocks', True) or data not in self.hideBlocks:
|
|
||||||
if data in clientAPI._core.getBlockList():
|
|
||||||
block = clientAPI.getBlockData(data, raw=True)
|
|
||||||
try:
|
|
||||||
block = block.encode()
|
|
||||||
except AttributeError:
|
|
||||||
abort(404)
|
|
||||||
block = clientAPI._core._utils.strToBytes(block)
|
|
||||||
resp = block
|
|
||||||
#resp = base64.b64encode(block).decode()
|
|
||||||
if len(resp) == 0:
|
|
||||||
abort(404)
|
|
||||||
resp = ""
|
|
||||||
return Response(resp, mimetype='application/octet-stream')
|
|
||||||
|
|
||||||
@app.route('/www/<path:path>')
|
@app.route('/www/<path:path>')
|
||||||
def wwwPublic(path):
|
def wwwPublic(path):
|
||||||
|
@ -163,40 +139,7 @@ class PublicAPI:
|
||||||
|
|
||||||
@app.route('/announce', methods=['post'])
|
@app.route('/announce', methods=['post'])
|
||||||
def acceptAnnounce():
|
def acceptAnnounce():
|
||||||
resp = 'failure'
|
resp = httpapi.miscpublicapi.announce(clientAPI, request)
|
||||||
powHash = ''
|
|
||||||
randomData = ''
|
|
||||||
newNode = ''
|
|
||||||
ourAdder = clientAPI._core.hsAddress.encode()
|
|
||||||
try:
|
|
||||||
newNode = request.form['node'].encode()
|
|
||||||
except KeyError:
|
|
||||||
logger.warn('No node specified for upload')
|
|
||||||
pass
|
|
||||||
else:
|
|
||||||
try:
|
|
||||||
randomData = request.form['random']
|
|
||||||
randomData = base64.b64decode(randomData)
|
|
||||||
except KeyError:
|
|
||||||
logger.warn('No random data specified for upload')
|
|
||||||
else:
|
|
||||||
nodes = newNode + clientAPI._core.hsAddress.encode()
|
|
||||||
nodes = clientAPI._core._crypto.blake2bHash(nodes)
|
|
||||||
powHash = clientAPI._core._crypto.blake2bHash(randomData + nodes)
|
|
||||||
try:
|
|
||||||
powHash = powHash.decode()
|
|
||||||
except AttributeError:
|
|
||||||
pass
|
|
||||||
if powHash.startswith('00000'):
|
|
||||||
newNode = clientAPI._core._utils.bytesToStr(newNode)
|
|
||||||
if clientAPI._core._utils.validateID(newNode) and not newNode in clientAPI._core.onionrInst.communicatorInst.newPeers:
|
|
||||||
clientAPI._core.onionrInst.communicatorInst.newPeers.append(newNode)
|
|
||||||
resp = 'Success'
|
|
||||||
else:
|
|
||||||
logger.warn(newNode.decode() + ' failed to meet POW: ' + powHash)
|
|
||||||
resp = Response(resp)
|
|
||||||
if resp == 'failure':
|
|
||||||
return resp, 406
|
|
||||||
return resp
|
return resp
|
||||||
|
|
||||||
@app.route('/upload', methods=['post'])
|
@app.route('/upload', methods=['post'])
|
||||||
|
@ -204,26 +147,7 @@ class PublicAPI:
|
||||||
'''Accept file uploads. In the future this will be done more often than on creation
|
'''Accept file uploads. In the future this will be done more often than on creation
|
||||||
to speed up block sync
|
to speed up block sync
|
||||||
'''
|
'''
|
||||||
resp = 'failure'
|
return httpapi.miscpublicapi.upload(clientAPI, request)
|
||||||
try:
|
|
||||||
data = request.form['block']
|
|
||||||
except KeyError:
|
|
||||||
logger.warn('No block specified for upload')
|
|
||||||
pass
|
|
||||||
else:
|
|
||||||
if sys.getsizeof(data) < 100000000:
|
|
||||||
try:
|
|
||||||
if blockimporter.importBlockFromData(data, clientAPI._core):
|
|
||||||
resp = 'success'
|
|
||||||
else:
|
|
||||||
logger.warn('Error encountered importing uploaded block')
|
|
||||||
except onionrexceptions.BlacklistedBlock:
|
|
||||||
logger.debug('uploaded block is blacklisted')
|
|
||||||
pass
|
|
||||||
if resp == 'failure':
|
|
||||||
abort(400)
|
|
||||||
resp = Response(resp)
|
|
||||||
return resp
|
|
||||||
|
|
||||||
# Set instances, then startup our public api server
|
# Set instances, then startup our public api server
|
||||||
clientAPI.setPublicAPIInstance(self)
|
clientAPI.setPublicAPIInstance(self)
|
||||||
|
|
|
@ -23,6 +23,8 @@ import base64, sqlite3, os
|
||||||
from dependencies import secrets
|
from dependencies import secrets
|
||||||
from utils import netutils
|
from utils import netutils
|
||||||
from onionrusers import onionrusers
|
from onionrusers import onionrusers
|
||||||
|
from etc import onionrvalues
|
||||||
|
ov = onionrvalues.OnionrValues()
|
||||||
|
|
||||||
class DaemonTools:
|
class DaemonTools:
|
||||||
'''
|
'''
|
||||||
|
@ -64,7 +66,8 @@ class DaemonTools:
|
||||||
if ourID != 1:
|
if ourID != 1:
|
||||||
#TODO: Extend existingRand for i2p
|
#TODO: Extend existingRand for i2p
|
||||||
existingRand = self.daemon._core.getAddressInfo(peer, 'powValue')
|
existingRand = self.daemon._core.getAddressInfo(peer, 'powValue')
|
||||||
if type(existingRand) is type(None):
|
# Reset existingRand if it no longer meets the minimum POW
|
||||||
|
if type(existingRand) is type(None) or not existingRand.endswith(b'0' * ov.announce_pow):
|
||||||
existingRand = ''
|
existingRand = ''
|
||||||
|
|
||||||
if peer in self.announceCache:
|
if peer in self.announceCache:
|
||||||
|
@ -73,7 +76,7 @@ class DaemonTools:
|
||||||
data['random'] = existingRand
|
data['random'] = existingRand
|
||||||
else:
|
else:
|
||||||
self.announceProgress[peer] = True
|
self.announceProgress[peer] = True
|
||||||
proof = onionrproofs.DataPOW(combinedNodes, forceDifficulty=5)
|
proof = onionrproofs.DataPOW(combinedNodes, forceDifficulty=ov.announce_pow)
|
||||||
del self.announceProgress[peer]
|
del self.announceProgress[peer]
|
||||||
try:
|
try:
|
||||||
data['random'] = base64.b64encode(proof.waitForResult()[1])
|
data['random'] = base64.b64encode(proof.waitForResult()[1])
|
||||||
|
|
|
@ -23,3 +23,4 @@ class OnionrValues:
|
||||||
self.passwordLength = 20
|
self.passwordLength = 20
|
||||||
self.blockMetadataLengths = {'meta': 1000, 'sig': 200, 'signer': 200, 'time': 10, 'pow': 1000, 'encryptType': 4, 'expire': 14} #TODO properly refine values to minimum needed
|
self.blockMetadataLengths = {'meta': 1000, 'sig': 200, 'signer': 200, 'time': 10, 'pow': 1000, 'encryptType': 4, 'expire': 14} #TODO properly refine values to minimum needed
|
||||||
self.default_expire = 2592000
|
self.default_expire = 2592000
|
||||||
|
self.announce_pow = 5
|
|
@ -0,0 +1,6 @@
|
||||||
|
from . import announce, upload, getblocks
|
||||||
|
|
||||||
|
announce = announce.handle_announce # endpoint handler for accepting peer announcements
|
||||||
|
upload = upload.accept_upload # endpoint handler for accepting public uploads
|
||||||
|
public_block_list = getblocks.get_public_block_list # endpoint handler for getting block lists
|
||||||
|
public_get_block_data = getblocks.get_block_data # endpoint handler for responding to peers requests for block data
|
|
@ -0,0 +1,63 @@
|
||||||
|
'''
|
||||||
|
Onionr - P2P Microblogging Platform & Social network
|
||||||
|
|
||||||
|
Handle announcements to the public API server
|
||||||
|
'''
|
||||||
|
'''
|
||||||
|
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 <https://www.gnu.org/licenses/>.
|
||||||
|
'''
|
||||||
|
import base64
|
||||||
|
from flask import Response
|
||||||
|
import logger
|
||||||
|
from etc import onionrvalues
|
||||||
|
def handle_announce(clientAPI, request):
|
||||||
|
'''
|
||||||
|
accept announcement posts, validating POW
|
||||||
|
clientAPI should be an instance of the clientAPI server running, request is a instance of a flask request
|
||||||
|
'''
|
||||||
|
resp = 'failure'
|
||||||
|
powHash = ''
|
||||||
|
randomData = ''
|
||||||
|
newNode = ''
|
||||||
|
ourAdder = clientAPI._core.hsAddress.encode()
|
||||||
|
try:
|
||||||
|
newNode = request.form['node'].encode()
|
||||||
|
except KeyError:
|
||||||
|
logger.warn('No node specified for upload')
|
||||||
|
pass
|
||||||
|
else:
|
||||||
|
try:
|
||||||
|
randomData = request.form['random']
|
||||||
|
randomData = base64.b64decode(randomData)
|
||||||
|
except KeyError:
|
||||||
|
logger.warn('No random data specified for upload')
|
||||||
|
else:
|
||||||
|
nodes = newNode + clientAPI._core.hsAddress.encode()
|
||||||
|
nodes = clientAPI._core._crypto.blake2bHash(nodes)
|
||||||
|
powHash = clientAPI._core._crypto.blake2bHash(randomData + nodes)
|
||||||
|
try:
|
||||||
|
powHash = powHash.decode()
|
||||||
|
except AttributeError:
|
||||||
|
pass
|
||||||
|
if powHash.startswith('0' * onionrvalues.OnionrValues().announce_pow):
|
||||||
|
newNode = clientAPI._core._utils.bytesToStr(newNode)
|
||||||
|
if clientAPI._core._utils.validateID(newNode) and not newNode in clientAPI._core.onionrInst.communicatorInst.newPeers:
|
||||||
|
clientAPI._core.onionrInst.communicatorInst.newPeers.append(newNode)
|
||||||
|
resp = 'Success'
|
||||||
|
else:
|
||||||
|
logger.warn(newNode.decode() + ' failed to meet POW: ' + powHash)
|
||||||
|
resp = Response(resp)
|
||||||
|
if resp == 'failure':
|
||||||
|
return resp, 406
|
||||||
|
return resp
|
|
@ -0,0 +1,50 @@
|
||||||
|
'''
|
||||||
|
Onionr - P2P Microblogging Platform & Social network
|
||||||
|
|
||||||
|
Public endpoints to get block data and lists
|
||||||
|
'''
|
||||||
|
'''
|
||||||
|
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 <https://www.gnu.org/licenses/>.
|
||||||
|
'''
|
||||||
|
from flask import Response, abort
|
||||||
|
import config
|
||||||
|
def get_public_block_list(clientAPI, publicAPI, request):
|
||||||
|
# Provide a list of our blocks, with a date offset
|
||||||
|
dateAdjust = request.args.get('date')
|
||||||
|
bList = clientAPI._core.getBlockList(dateRec=dateAdjust)
|
||||||
|
if config.get('general.hide_created_blocks', True):
|
||||||
|
for b in publicAPI.hideBlocks:
|
||||||
|
if b in bList:
|
||||||
|
# Don't share blocks we created if they haven't been *uploaded* yet, makes it harder to find who created a block
|
||||||
|
bList.remove(b)
|
||||||
|
return Response('\n'.join(bList))
|
||||||
|
|
||||||
|
def get_block_data(clientAPI, publicAPI, data):
|
||||||
|
'''data is the block hash in hex'''
|
||||||
|
resp = ''
|
||||||
|
if clientAPI._utils.validateHash(data):
|
||||||
|
if not config.get('general.hide_created_blocks', True) or data not in publicAPI.hideBlocks:
|
||||||
|
if data in clientAPI._core.getBlockList():
|
||||||
|
block = clientAPI.getBlockData(data, raw=True)
|
||||||
|
try:
|
||||||
|
block = block.encode() # Encode in case data is binary
|
||||||
|
except AttributeError:
|
||||||
|
abort(404)
|
||||||
|
block = clientAPI._core._utils.strToBytes(block)
|
||||||
|
resp = block
|
||||||
|
if len(resp) == 0:
|
||||||
|
abort(404)
|
||||||
|
resp = ""
|
||||||
|
# Has to be octet stream, otherwise binary data fails hash check
|
||||||
|
return Response(resp, mimetype='application/octet-stream')
|
|
@ -0,0 +1,43 @@
|
||||||
|
'''
|
||||||
|
Onionr - P2P Microblogging Platform & Social network
|
||||||
|
|
||||||
|
Accept block uploads to the public API server
|
||||||
|
'''
|
||||||
|
'''
|
||||||
|
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 <https://www.gnu.org/licenses/>.
|
||||||
|
'''
|
||||||
|
import sys
|
||||||
|
from flask import Response, abort
|
||||||
|
import blockimporter, onionrexceptions, logger
|
||||||
|
def accept_upload(clientAPI, request):
|
||||||
|
resp = 'failure'
|
||||||
|
try:
|
||||||
|
data = request.form['block']
|
||||||
|
except KeyError:
|
||||||
|
logger.warn('No block specified for upload')
|
||||||
|
pass
|
||||||
|
else:
|
||||||
|
if sys.getsizeof(data) < 100000000:
|
||||||
|
try:
|
||||||
|
if blockimporter.importBlockFromData(data, clientAPI._core):
|
||||||
|
resp = 'success'
|
||||||
|
else:
|
||||||
|
logger.warn('Error encountered importing uploaded block')
|
||||||
|
except onionrexceptions.BlacklistedBlock:
|
||||||
|
logger.debug('uploaded block is blacklisted')
|
||||||
|
pass
|
||||||
|
if resp == 'failure':
|
||||||
|
abort(400)
|
||||||
|
resp = Response(resp)
|
||||||
|
return resp
|
|
@ -401,6 +401,8 @@ class Onionr:
|
||||||
'''
|
'''
|
||||||
Starts the Onionr daemon
|
Starts the Onionr daemon
|
||||||
'''
|
'''
|
||||||
|
if config.get('general.dev_mode', False):
|
||||||
|
override = True
|
||||||
commands.daemonlaunch.start(self, input, override)
|
commands.daemonlaunch.start(self, input, override)
|
||||||
|
|
||||||
def setClientAPIInst(self, inst):
|
def setClientAPIInst(self, inst):
|
||||||
|
|
|
@ -29,12 +29,12 @@
|
||||||
<br><br><a class='idLink' href='/mail/'>Mail</a> - <a class='idLink' href='/friends/'>Friend Manager</a> - <a class='idLink' href='/board/'>Boards</a>
|
<br><br><a class='idLink' href='/mail/'>Mail</a> - <a class='idLink' href='/friends/'>Friend Manager</a> - <a class='idLink' href='/board/'>Boards</a>
|
||||||
<br><br><hr>
|
<br><br><hr>
|
||||||
<details class='configArea'>
|
<details class='configArea'>
|
||||||
<summary><b>Edit Configuration</b></summary>
|
<summary><b>Edit Configuration</b></summary>
|
||||||
<br>
|
<br>
|
||||||
<p><em>Warning: </em><b>Some values can be dangerous to change. Use caution.</b></p>
|
<p><em>Warning: </em><b>Some values can be dangerous to change. Use caution.</b></p>
|
||||||
<br>
|
<br>
|
||||||
<textarea class='configEditor'></textarea>
|
<textarea class='configEditor'></textarea>
|
||||||
<button class='saveConfig successBtn'>Save Config</button>
|
<button class='saveConfig successBtn'>Save Config</button>
|
||||||
</details>
|
</details>
|
||||||
<hr>
|
<hr>
|
||||||
<h2>Stats</h2>
|
<h2>Stats</h2>
|
||||||
|
|
Loading…
Reference in New Issue