prevent replay of very old encrypted data
This commit is contained in:
parent
31039861c2
commit
651fc8c43c
Binary file not shown.
Before Width: | Height: | Size: 191 KiB |
@ -91,6 +91,22 @@ In addition, randomness beacons such as the one operated by [NIST](https://beaco
|
|||||||
|
|
||||||
# Direct Connections
|
# 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.
|
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.
|
@ -689,6 +689,8 @@ class Core:
|
|||||||
return False
|
return False
|
||||||
retData = False
|
retData = False
|
||||||
|
|
||||||
|
createTime = self._utils.getRoundedEpoch()
|
||||||
|
|
||||||
# check nonce
|
# check nonce
|
||||||
dataNonce = self._utils.bytesToStr(self._crypto.sha3Hash(data))
|
dataNonce = self._utils.bytesToStr(self._crypto.sha3Hash(data))
|
||||||
try:
|
try:
|
||||||
@ -706,10 +708,7 @@ class Core:
|
|||||||
data = str(data)
|
data = str(data)
|
||||||
plaintext = data
|
plaintext = data
|
||||||
plaintextMeta = {}
|
plaintextMeta = {}
|
||||||
|
plaintextPeer = asymPeer
|
||||||
# Convert asym peer human readable key to base32 if set
|
|
||||||
if ' ' in asymPeer.strip():
|
|
||||||
asymPeer = self._utils.convertHumanReadableID(asymPeer)
|
|
||||||
|
|
||||||
retData = ''
|
retData = ''
|
||||||
signature = ''
|
signature = ''
|
||||||
@ -732,6 +731,7 @@ class Core:
|
|||||||
pass
|
pass
|
||||||
|
|
||||||
if encryptType == 'asym':
|
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:
|
if not disableForward and sign and asymPeer != self._crypto.pubKey:
|
||||||
try:
|
try:
|
||||||
forwardEncrypted = onionrusers.OnionrUser(self, asymPeer).forwardEncrypt(data)
|
forwardEncrypted = onionrusers.OnionrUser(self, asymPeer).forwardEncrypt(data)
|
||||||
@ -779,7 +779,7 @@ class Core:
|
|||||||
metadata['meta'] = jsonMeta
|
metadata['meta'] = jsonMeta
|
||||||
metadata['sig'] = signature
|
metadata['sig'] = signature
|
||||||
metadata['signer'] = signer
|
metadata['signer'] = signer
|
||||||
metadata['time'] = self._utils.getRoundedEpoch()
|
metadata['time'] = createTime
|
||||||
|
|
||||||
# ensure expire is integer and of sane length
|
# ensure expire is integer and of sane length
|
||||||
if type(expire) is not type(None):
|
if type(expire) is not type(None):
|
||||||
@ -804,6 +804,9 @@ class Core:
|
|||||||
self.daemonQueueAdd('uploadBlock', retData)
|
self.daemonQueueAdd('uploadBlock', retData)
|
||||||
|
|
||||||
if retData != False:
|
if retData != False:
|
||||||
|
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)
|
events.event('insertblock', {'content': plaintext, 'meta': plaintextMeta, 'hash': retData, 'peer': self._utils.bytesToStr(asymPeer)}, onionr = self.onionrInst, threaded = True)
|
||||||
return retData
|
return retData
|
||||||
|
|
||||||
|
@ -26,7 +26,7 @@ class Block:
|
|||||||
blockCacheOrder = list() # NEVER write your own code that writes to this!
|
blockCacheOrder = list() # NEVER write your own code that writes to this!
|
||||||
blockCache = dict() # should never be accessed directly, look at Block.getCache()
|
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
|
# take from arguments
|
||||||
# sometimes people input a bytes object instead of str in `hash`
|
# sometimes people input a bytes object instead of str in `hash`
|
||||||
if (not hash is None) and isinstance(hash, bytes):
|
if (not hash is None) and isinstance(hash, bytes):
|
||||||
@ -37,6 +37,7 @@ class Block:
|
|||||||
self.btype = type
|
self.btype = type
|
||||||
self.bcontent = content
|
self.bcontent = content
|
||||||
self.expire = expire
|
self.expire = expire
|
||||||
|
self.bypassReplayCheck = bypassReplayCheck
|
||||||
|
|
||||||
# initialize variables
|
# initialize variables
|
||||||
self.valid = True
|
self.valid = True
|
||||||
@ -84,6 +85,19 @@ class Block:
|
|||||||
self.signer = core._crypto.pubKeyDecrypt(self.signer, encodedData=encodedData)
|
self.signer = core._crypto.pubKeyDecrypt(self.signer, encodedData=encodedData)
|
||||||
self.bheader['signer'] = self.signer.decode()
|
self.bheader['signer'] = self.signer.decode()
|
||||||
self.signedData = json.dumps(self.bmetadata) + self.bcontent.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:
|
try:
|
||||||
assert self.bmetadata['forwardEnc'] is True
|
assert self.bmetadata['forwardEnc'] is True
|
||||||
except (AssertionError, KeyError) as e:
|
except (AssertionError, KeyError) as e:
|
||||||
@ -97,6 +111,8 @@ class Block:
|
|||||||
except nacl.exceptions.CryptoError:
|
except nacl.exceptions.CryptoError:
|
||||||
pass
|
pass
|
||||||
#logger.debug('Could not decrypt block. Either invalid key or corrupted data')
|
#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:
|
else:
|
||||||
retData = True
|
retData = True
|
||||||
self.decrypted = True
|
self.decrypted = True
|
||||||
|
@ -264,6 +264,13 @@ class OnionrCrypto:
|
|||||||
|
|
||||||
return retData
|
return retData
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def replayTimestampValidation(timestamp):
|
||||||
|
if core.Core()._utils.getEpoch() - int(timestamp) > 2419200:
|
||||||
|
return False
|
||||||
|
else:
|
||||||
|
return True
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def safeCompare(one, two):
|
def safeCompare(one, two):
|
||||||
# Do encode here to avoid spawning core
|
# Do encode here to avoid spawning core
|
||||||
|
@ -198,8 +198,8 @@ class DaemonTools:
|
|||||||
fakePeer = ''
|
fakePeer = ''
|
||||||
chance = 10
|
chance = 10
|
||||||
if secrets.randbelow(chance) == (chance - 1):
|
if secrets.randbelow(chance) == (chance - 1):
|
||||||
fakePeer = self.daemon._core._crypto.generatePubKey()[0]
|
fakePeer = 'OVPCZLOXD6DC5JHX4EQ3PSOGAZ3T24F75HQLIUZSDSMYPEOXCPFA===='
|
||||||
data = secrets.token_hex(secrets.randbelow(500) + 1)
|
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')
|
self.daemon.decrementThreadCount('insertDeniableBlock')
|
||||||
return
|
return
|
@ -45,6 +45,9 @@ class PasswordStrengthError(Exception):
|
|||||||
|
|
||||||
# block exceptions
|
# block exceptions
|
||||||
|
|
||||||
|
class ReplayAttack(Exception):
|
||||||
|
pass
|
||||||
|
|
||||||
class DifficultyTooLarge(Exception):
|
class DifficultyTooLarge(Exception):
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
@ -284,6 +284,12 @@ class OnionrUtils:
|
|||||||
except AssertionError:
|
except AssertionError:
|
||||||
logger.warn('Block is expired')
|
logger.warn('Block is expired')
|
||||||
break
|
break
|
||||||
|
elif i == 'encryptType':
|
||||||
|
try:
|
||||||
|
assert metadata[i] in ('asym', 'sym', '')
|
||||||
|
except AssertionError:
|
||||||
|
logger.warn('Invalid encryption mode')
|
||||||
|
break
|
||||||
else:
|
else:
|
||||||
# if metadata loop gets no errors, it does not break, therefore metadata is valid
|
# 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)
|
# make sure we do not have another block with the same data content (prevent data duplication and replay attacks)
|
||||||
|
@ -43,7 +43,6 @@ def draw_border(text):
|
|||||||
res.append('└' + '─' * width + '┘')
|
res.append('└' + '─' * width + '┘')
|
||||||
return '\n'.join(res)
|
return '\n'.join(res)
|
||||||
|
|
||||||
|
|
||||||
class MailStrings:
|
class MailStrings:
|
||||||
def __init__(self, mailInstance):
|
def __init__(self, mailInstance):
|
||||||
self.mailInstance = mailInstance
|
self.mailInstance = mailInstance
|
||||||
|
@ -48,6 +48,7 @@
|
|||||||
</div>
|
</div>
|
||||||
<div id='sendMessage' class='overlay'>
|
<div id='sendMessage' class='overlay'>
|
||||||
<div class='overlayContent'>
|
<div class='overlayContent'>
|
||||||
|
<label>Select friend: <select id='friendSelect'></select></label>
|
||||||
<form method='post' action='/apipoints/mail/send' id='sendForm' enctype="application/x-www-form-urlencoded">
|
<form method='post' action='/apipoints/mail/send' id='sendForm' enctype="application/x-www-form-urlencoded">
|
||||||
<span class='closeOverlay' overlay='sendMessage'></span>
|
<span class='closeOverlay' overlay='sendMessage'></span>
|
||||||
To: <input id='draftID' type='text' name='to' placeholder='pubkey' required>
|
To: <input id='draftID' type='text' name='to' placeholder='pubkey' required>
|
||||||
|
@ -78,7 +78,7 @@ function loadInboxEntrys(bHash){
|
|||||||
var metadata = resp['metadata']
|
var metadata = resp['metadata']
|
||||||
humanDate.setUTCSeconds(resp['meta']['time'])
|
humanDate.setUTCSeconds(resp['meta']['time'])
|
||||||
if (resp['meta']['signer'] != ''){
|
if (resp['meta']['signer'] != ''){
|
||||||
senderInput.value = httpGet('/getHumanReadable/' + resp['meta']['signer'])
|
senderInput.value = httpGet('/friends/getinfo/' + resp['meta']['signer'] + '/name')
|
||||||
}
|
}
|
||||||
if (resp['meta']['validSig']){
|
if (resp['meta']['validSig']){
|
||||||
validSig.innerText = 'Signature Validity: Good'
|
validSig.innerText = 'Signature Validity: Good'
|
||||||
@ -88,7 +88,7 @@ function loadInboxEntrys(bHash){
|
|||||||
validSig.style.color = 'red'
|
validSig.style.color = 'red'
|
||||||
}
|
}
|
||||||
if (senderInput.value == ''){
|
if (senderInput.value == ''){
|
||||||
senderInput.value = 'Anonymous'
|
senderInput.value = resp['meta']['signer']
|
||||||
}
|
}
|
||||||
bHashDisplay.innerText = bHash.substring(0, 10)
|
bHashDisplay.innerText = bHash.substring(0, 10)
|
||||||
entry.setAttribute('hash', bHash)
|
entry.setAttribute('hash', bHash)
|
||||||
@ -195,17 +195,18 @@ tabBtns.onclick = function(event){
|
|||||||
setActiveTab(event.target.innerText.toLowerCase())
|
setActiveTab(event.target.innerText.toLowerCase())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
var idStrings = document.getElementsByClassName('myPub')
|
var idStrings = document.getElementsByClassName('myPub')
|
||||||
var myHumanReadable = httpGet('/getHumanReadable/' + myPub)
|
|
||||||
for (var i = 0; i < idStrings.length; i++){
|
for (var i = 0; i < idStrings.length; i++){
|
||||||
if (idStrings[i].tagName.toLowerCase() == 'input'){
|
if (idStrings[i].tagName.toLowerCase() == 'input'){
|
||||||
idStrings[i].value = myHumanReadable
|
idStrings[i].value = myPub
|
||||||
}
|
}
|
||||||
else{
|
else{
|
||||||
idStrings[i].innerText = myHumanReadable
|
idStrings[i].innerText = myPub
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
for (var i = 0; i < document.getElementsByClassName('refresh').length; i++){
|
for (var i = 0; i < document.getElementsByClassName('refresh').length; i++){
|
||||||
document.getElementsByClassName('refresh')[i].style.float = 'right'
|
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'])
|
||||||
|
}
|
||||||
|
})
|
@ -18,6 +18,10 @@
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
var sendbutton = document.getElementById('sendMail')
|
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){
|
function sendMail(to, message, subject){
|
||||||
//postData = {"postData": '{"to": "' + to + '", "message": "' + message + '"}'} // galaxy brain
|
//postData = {"postData": '{"to": "' + to + '", "message": "' + message + '"}'} // galaxy brain
|
||||||
@ -35,11 +39,19 @@ function sendMail(to, message, subject){
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var friendPicker = document.getElementById('friendSelect')
|
||||||
|
friendPicker.onchange = function(){
|
||||||
|
to.value = friendPicker.value
|
||||||
|
}
|
||||||
|
|
||||||
sendForm.onsubmit = function(){
|
sendForm.onsubmit = function(){
|
||||||
var messageContent = document.getElementById('draftText')
|
if (friendPicker.value.trim().length !== 0 && to.value.trim().length !== 0){
|
||||||
var to = document.getElementById('draftID')
|
if (friendPicker.value !== to.value){
|
||||||
var subject = document.getElementById('draftSubject')
|
alert('You have selected both a friend and entered a public key manually.')
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
sendMail(to.value, messageContent.value, subject.value)
|
sendMail(to.value, messageContent.value, subject.value)
|
||||||
return false;
|
return false
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user