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

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
}