prevent replay of very old encrypted data
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
|
||||
|
||||
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
|
||||
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):
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
|
@ -45,6 +45,9 @@ class PasswordStrengthError(Exception):
|
|||
|
||||
# block exceptions
|
||||
|
||||
class ReplayAttack(Exception):
|
||||
pass
|
||||
|
||||
class DifficultyTooLarge(Exception):
|
||||
pass
|
||||
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -43,7 +43,6 @@ def draw_border(text):
|
|||
res.append('└' + '─' * width + '┘')
|
||||
return '\n'.join(res)
|
||||
|
||||
|
||||
class MailStrings:
|
||||
def __init__(self, mailInstance):
|
||||
self.mailInstance = mailInstance
|
||||
|
|
|
@ -48,6 +48,7 @@
|
|||
</div>
|
||||
<div id='sendMessage' class='overlay'>
|
||||
<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">
|
||||
<span class='closeOverlay' overlay='sendMessage'></span>
|
||||
To: <input id='draftID' type='text' name='to' placeholder='pubkey' required>
|
||||
|
|
|
@ -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'])
|
||||
}
|
||||
})
|
|
@ -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
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue