prevent replay of very old encrypted data

This commit is contained in:
Kevin Froman 2019-02-25 22:19:37 -06:00
parent 31039861c2
commit 651fc8c43c
12 changed files with 119 additions and 24 deletions

Binary file not shown.

Before

Width:  |  Height:  |  Size: 191 KiB

View File

@ -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.

View File

@ -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,7 +804,10 @@ class Core:
self.daemonQueueAdd('uploadBlock', retData) self.daemonQueueAdd('uploadBlock', retData)
if retData != False: 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 return retData
def introduceNode(self): def introduceNode(self):

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -45,6 +45,9 @@ class PasswordStrengthError(Exception):
# block exceptions # block exceptions
class ReplayAttack(Exception):
pass
class DifficultyTooLarge(Exception): class DifficultyTooLarge(Exception):
pass pass

View File

@ -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)

View File

@ -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

View File

@ -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>

View File

@ -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'])
}
})

View File

@ -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
} }