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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -43,7 +43,6 @@ def draw_border(text):
res.append('' + '' * width + '')
return '\n'.join(res)
class MailStrings:
def __init__(self, mailInstance):
self.mailInstance = mailInstance

View File

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

View File

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

View File

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