diff --git a/docs/onionr-logo.png~ b/docs/onionr-logo.png~ deleted file mode 100755 index 5e15d42f..00000000 Binary files a/docs/onionr-logo.png~ and /dev/null differ diff --git a/docs/whitepaper.md b/docs/whitepaper.md index 8c5d0f96..f7ed9fa8 100755 --- a/docs/whitepaper.md +++ b/docs/whitepaper.md @@ -91,6 +91,22 @@ In addition, randomness beacons such as the one operated by [NIST](https://beaco # Direct Connections -We propose a method of using Onionr's block sync system to enable direct connections between peers by having one peer request to connect to another using the peer's public key. Since the request is within a standard block, proof of work must be used to request connection. If the requested peer is available and wishes to accept the connection,Onionr will generate a temporary .onion address for the other peer to connect to. Alternatively, a reverse connection may be formed, which is faster to establish but requires a message brokering system instead of a standard socket. +We propose a method of using Onionr's block sync system to enable direct connections between peers by having one peer request to connect to another using the peer's public key. Since the request is within a standard block, proof of work must be used to request connection. If the requested peer is available and wishes to accept the connection, Onionr will generate a temporary .onion address for the other peer to connect to. Alternatively, a reverse connection may be formed, which is faster to establish but requires a message brokering system instead of a standard socket. -The benefits of such a system are increased privacy, and the ability to anonymously communicate from multiple devices at once. In a traditional onion service, one's online status can be monitored and more easily correlated. \ No newline at end of file +The benefits of such a system are increased privacy, and the ability to anonymously communicate from multiple devices at once. In a traditional onion service, one's online status can be monitored and more easily correlated. + +# Threat Model + +The goal of Onionr is to provide a method of distributing information in a manner in which the difficulty of discovering the identity of those sending and receiving the information is greatly increased. In this section we detail what information we want to protect and who we're protecting it from. + +In this threat model, "protected" means available in plaintext only to those which it was intended, and regardless non-malleable + +## Threat Actors + +Onionr assumes that traffic/data is being surveilled by a multitude of actors on every level but the local machine. Some examples of threat actors that we seek to protect against include Internet service providers, local area network administrators, + +## Assumptions + +We assume that Tor onion services (v3) and I2P services cannot be trivially deanonymized, and that the cryptographic algorithms we employ cannot be broken in any manner faster than brute force unless a quantum computer is used. + +Once supposed quantum safe algorithms are more mature and have relatively high level libraries, they will be deployed. \ No newline at end of file diff --git a/onionr/core.py b/onionr/core.py index 396ebca4..6b2e1505 100755 --- a/onionr/core.py +++ b/onionr/core.py @@ -689,6 +689,8 @@ class Core: return False retData = False + createTime = self._utils.getRoundedEpoch() + # check nonce dataNonce = self._utils.bytesToStr(self._crypto.sha3Hash(data)) try: @@ -706,10 +708,7 @@ class Core: data = str(data) plaintext = data plaintextMeta = {} - - # Convert asym peer human readable key to base32 if set - if ' ' in asymPeer.strip(): - asymPeer = self._utils.convertHumanReadableID(asymPeer) + plaintextPeer = asymPeer retData = '' signature = '' @@ -732,6 +731,7 @@ class Core: pass if encryptType == 'asym': + meta['rply'] = createTime # Duplicate the time in encrypted messages to prevent replays if not disableForward and sign and asymPeer != self._crypto.pubKey: try: forwardEncrypted = onionrusers.OnionrUser(self, asymPeer).forwardEncrypt(data) @@ -779,7 +779,7 @@ class Core: metadata['meta'] = jsonMeta metadata['sig'] = signature metadata['signer'] = signer - metadata['time'] = self._utils.getRoundedEpoch() + metadata['time'] = createTime # ensure expire is integer and of sane length if type(expire) is not type(None): @@ -804,7 +804,10 @@ class Core: self.daemonQueueAdd('uploadBlock', retData) if retData != False: - events.event('insertblock', {'content': plaintext, 'meta': plaintextMeta, 'hash': retData, 'peer': self._utils.bytesToStr(asymPeer)}, onionr = self.onionrInst, threaded = True) + if plaintextPeer == 'OVPCZLOXD6DC5JHX4EQ3PSOGAZ3T24F75HQLIUZSDSMYPEOXCPFA====': + events.event('insertdeniable', {'content': plaintext, 'meta': plaintextMeta, 'hash': retData, 'peer': self._utils.bytesToStr(asymPeer)}, onionr = self.onionrInst, threaded = True) + else: + events.event('insertblock', {'content': plaintext, 'meta': plaintextMeta, 'hash': retData, 'peer': self._utils.bytesToStr(asymPeer)}, onionr = self.onionrInst, threaded = True) return retData def introduceNode(self): diff --git a/onionr/onionrblockapi.py b/onionr/onionrblockapi.py index 6921ab90..caa9bee1 100755 --- a/onionr/onionrblockapi.py +++ b/onionr/onionrblockapi.py @@ -26,7 +26,7 @@ class Block: blockCacheOrder = list() # NEVER write your own code that writes to this! blockCache = dict() # should never be accessed directly, look at Block.getCache() - def __init__(self, hash = None, core = None, type = None, content = None, expire=None, decrypt=False): + def __init__(self, hash = None, core = None, type = None, content = None, expire=None, decrypt=False, bypassReplayCheck=False): # take from arguments # sometimes people input a bytes object instead of str in `hash` if (not hash is None) and isinstance(hash, bytes): @@ -37,6 +37,7 @@ class Block: self.btype = type self.bcontent = content self.expire = expire + self.bypassReplayCheck = bypassReplayCheck # initialize variables self.valid = True @@ -84,6 +85,19 @@ class Block: self.signer = core._crypto.pubKeyDecrypt(self.signer, encodedData=encodedData) self.bheader['signer'] = self.signer.decode() self.signedData = json.dumps(self.bmetadata) + self.bcontent.decode() + + # Check for replay attacks + try: + assert self.core._crypto.replayTimestampValidation(self.bmetadata['rply']) + except (AssertionError, KeyError) as e: + if not self.bypassReplayCheck: + # Zero out variables to prevent reading of replays + self.bmetadata = {} + self.signer = '' + self.bheader['signer'] = '' + self.signedData = '' + self.signature = '' + raise onionrexceptions.ReplayAttack('Signature is too old. possible replay attack') try: assert self.bmetadata['forwardEnc'] is True except (AssertionError, KeyError) as e: @@ -97,6 +111,8 @@ class Block: except nacl.exceptions.CryptoError: pass #logger.debug('Could not decrypt block. Either invalid key or corrupted data') + except onionrexceptions.ReplayAttack: + logger.warn('%s is possibly a replay attack' % (self.hash,)) else: retData = True self.decrypted = True diff --git a/onionr/onionrcrypto.py b/onionr/onionrcrypto.py index 8f6f8e7a..4b5a72e1 100755 --- a/onionr/onionrcrypto.py +++ b/onionr/onionrcrypto.py @@ -264,6 +264,13 @@ class OnionrCrypto: return retData + @staticmethod + def replayTimestampValidation(timestamp): + if core.Core()._utils.getEpoch() - int(timestamp) > 2419200: + return False + else: + return True + @staticmethod def safeCompare(one, two): # Do encode here to avoid spawning core diff --git a/onionr/onionrdaemontools.py b/onionr/onionrdaemontools.py index 73cdf0f1..70b405f7 100755 --- a/onionr/onionrdaemontools.py +++ b/onionr/onionrdaemontools.py @@ -198,8 +198,8 @@ class DaemonTools: fakePeer = '' chance = 10 if secrets.randbelow(chance) == (chance - 1): - fakePeer = self.daemon._core._crypto.generatePubKey()[0] + fakePeer = 'OVPCZLOXD6DC5JHX4EQ3PSOGAZ3T24F75HQLIUZSDSMYPEOXCPFA====' data = secrets.token_hex(secrets.randbelow(500) + 1) - self.daemon._core.insertBlock(data, header='db', encryptType='asym', asymPeer=fakePeer, meta={'subject': 'foo'}) + self.daemon._core.insertBlock(data, header='pm', encryptType='asym', asymPeer=fakePeer, meta={'subject': 'foo'}) self.daemon.decrementThreadCount('insertDeniableBlock') return \ No newline at end of file diff --git a/onionr/onionrexceptions.py b/onionr/onionrexceptions.py index 757091db..5bb82c6c 100755 --- a/onionr/onionrexceptions.py +++ b/onionr/onionrexceptions.py @@ -45,6 +45,9 @@ class PasswordStrengthError(Exception): # block exceptions +class ReplayAttack(Exception): + pass + class DifficultyTooLarge(Exception): pass diff --git a/onionr/onionrutils.py b/onionr/onionrutils.py index afe834e8..23d265d7 100755 --- a/onionr/onionrutils.py +++ b/onionr/onionrutils.py @@ -284,6 +284,12 @@ class OnionrUtils: except AssertionError: logger.warn('Block is expired') break + elif i == 'encryptType': + try: + assert metadata[i] in ('asym', 'sym', '') + except AssertionError: + logger.warn('Invalid encryption mode') + break else: # if metadata loop gets no errors, it does not break, therefore metadata is valid # make sure we do not have another block with the same data content (prevent data duplication and replay attacks) diff --git a/onionr/static-data/default-plugins/pms/main.py b/onionr/static-data/default-plugins/pms/main.py index a5efab12..d0552a94 100755 --- a/onionr/static-data/default-plugins/pms/main.py +++ b/onionr/static-data/default-plugins/pms/main.py @@ -43,7 +43,6 @@ def draw_border(text): res.append('└' + '─' * width + '┘') return '\n'.join(res) - class MailStrings: def __init__(self, mailInstance): self.mailInstance = mailInstance diff --git a/onionr/static-data/www/mail/index.html b/onionr/static-data/www/mail/index.html index 1b1c0e84..53530968 100755 --- a/onionr/static-data/www/mail/index.html +++ b/onionr/static-data/www/mail/index.html @@ -48,6 +48,7 @@
+
To: diff --git a/onionr/static-data/www/mail/mail.js b/onionr/static-data/www/mail/mail.js index 4513ce9b..c7f266ce 100755 --- a/onionr/static-data/www/mail/mail.js +++ b/onionr/static-data/www/mail/mail.js @@ -78,7 +78,7 @@ function loadInboxEntrys(bHash){ var metadata = resp['metadata'] humanDate.setUTCSeconds(resp['meta']['time']) if (resp['meta']['signer'] != ''){ - senderInput.value = httpGet('/getHumanReadable/' + resp['meta']['signer']) + senderInput.value = httpGet('/friends/getinfo/' + resp['meta']['signer'] + '/name') } if (resp['meta']['validSig']){ validSig.innerText = 'Signature Validity: Good' @@ -88,7 +88,7 @@ function loadInboxEntrys(bHash){ validSig.style.color = 'red' } if (senderInput.value == ''){ - senderInput.value = 'Anonymous' + senderInput.value = resp['meta']['signer'] } bHashDisplay.innerText = bHash.substring(0, 10) entry.setAttribute('hash', bHash) @@ -195,17 +195,18 @@ tabBtns.onclick = function(event){ setActiveTab(event.target.innerText.toLowerCase()) } + var idStrings = document.getElementsByClassName('myPub') -var myHumanReadable = httpGet('/getHumanReadable/' + myPub) for (var i = 0; i < idStrings.length; i++){ if (idStrings[i].tagName.toLowerCase() == 'input'){ - idStrings[i].value = myHumanReadable + idStrings[i].value = myPub } else{ - idStrings[i].innerText = myHumanReadable + idStrings[i].innerText = myPub } } + for (var i = 0; i < document.getElementsByClassName('refresh').length; i++){ document.getElementsByClassName('refresh')[i].style.float = 'right' } @@ -216,3 +217,34 @@ for (var i = 0; i < document.getElementsByClassName('closeOverlay').length; i++) } } +fetch('/friends/list', { + headers: { + "token": webpass + }}) +.then((resp) => resp.json()) // Transform the data into json +.then(function(resp) { + var friendSelectParent = document.getElementById('friendSelect') + var keys = []; + var friend + for(var k in resp) keys.push(k); + + friendSelectParent.appendChild(document.createElement('option')) + for (var i = 0; i < keys.length; i++) { + var option = document.createElement("option") + var name = resp[keys[i]]['name'] + option.value = keys[i] + if (name.length == 0){ + option.text = keys[i] + } + else{ + option.text = name + } + friendSelectParent.appendChild(option) + } + + for (var i = 0; i < keys.length; i++){ + + //friendSelectParent + //alert(resp[keys[i]]['name']) + } +}) \ No newline at end of file diff --git a/onionr/static-data/www/mail/sendmail.js b/onionr/static-data/www/mail/sendmail.js index 945c7792..abece988 100644 --- a/onionr/static-data/www/mail/sendmail.js +++ b/onionr/static-data/www/mail/sendmail.js @@ -18,6 +18,10 @@ */ var sendbutton = document.getElementById('sendMail') +messageContent = document.getElementById('draftText') +to = document.getElementById('draftID') +subject = document.getElementById('draftSubject') +friendPicker = document.getElementById('friendSelect') function sendMail(to, message, subject){ //postData = {"postData": '{"to": "' + to + '", "message": "' + message + '"}'} // galaxy brain @@ -35,11 +39,19 @@ function sendMail(to, message, subject){ }) } -sendForm.onsubmit = function(){ - var messageContent = document.getElementById('draftText') - var to = document.getElementById('draftID') - var subject = document.getElementById('draftSubject') - - sendMail(to.value, messageContent.value, subject.value) - return false; +var friendPicker = document.getElementById('friendSelect') +friendPicker.onchange = function(){ + to.value = friendPicker.value +} + +sendForm.onsubmit = function(){ + if (friendPicker.value.trim().length !== 0 && to.value.trim().length !== 0){ + if (friendPicker.value !== to.value){ + alert('You have selected both a friend and entered a public key manually.') + return false + } + } + + sendMail(to.value, messageContent.value, subject.value) + return false }