Skip to content

Commit

Permalink
Sign form to ensure it is not tampered with
Browse files Browse the repository at this point in the history
This adds a server secret and uses it to sign the generated url with a
timestamp to prevent client-side tampering.

fixes atoponce#57
  • Loading branch information
ekoyle committed Jan 22, 2020
1 parent 93e62c7 commit 71f0025
Show file tree
Hide file tree
Showing 5 changed files with 59 additions and 3 deletions.
11 changes: 10 additions & 1 deletion dnote/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ def index():
"""Return the index.html for the main application."""
error = request.args.get('error', None)
note = Note()
return render_template('index.html', random=note.url, error=error)
return render_template('index.html', note=note, error=error)

@DNOTE.route('/security/', methods=['GET'])
def security():
Expand All @@ -33,6 +33,9 @@ def about():
def show_post():
"""Return the random URL after posting the plaintext."""
new_url = request.form["new_url"]
timestamp = request.form["timestamp"]
signature = request.form["signature"]

note = Note(new_url)
note.plaintext = request.form['paste']

Expand All @@ -42,6 +45,12 @@ def show_post():
if not utils.verify_hashcash(token):
return redirect(url_for('index', error='hashcash'))

try:
note.validate_signature(url=new_url, timestamp=timestamp,
provided_signature=signature)
except Exception as e:
return render_template('signerror.html', error=e)

if passphrase:
note.set_passphrase(passphrase)
note.encrypt()
Expand Down
38 changes: 37 additions & 1 deletion dnote/note.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,10 @@
import base64
import os
import sys
import time
import zlib
from Crypto.Cipher import AES
from Crypto.Hash import HMAC, SHA512
from Crypto.Hash import HMAC, SHA512, SHA384
from Crypto.Protocol import KDF
from Crypto.Util import Counter

Expand Down Expand Up @@ -60,13 +61,48 @@ class Note(object):
dkey = None # Duress passphrase
plaintext = None # Plain text note
ciphertext = None # Encrypted text
timestamp = None # current time
signature = None # signature

def __init__(self, url=None):
if url is None:
self.create_url()
self.sign_note()
else:
self.decode_url(url)

@staticmethod
def validate_signature(url, timestamp, provided_signature):
# Raise an exception for invalid signature

if url is None or timestamp is None:
raise ValueError("timestamp and/or url are None")

delta = int(time.time()) - int(float(timestamp))
if delta < 0 or delta > dconfig.signature_validity:
raise ValueError("Signature has expired")

computed_signature = Note.get_signature(url=url, timestamp=timestamp)

# FIXME: requires python3.3
#if not HMAC.compare_digest(computed_signature, provided_signature):
if computed_signature != provided_signature:
raise ValueError("Invalid signature provided!")

def sign_note(self):
if self.url is None:
raise ValueError("url has not been set")

self.timestamp = str(time.time())

self.signature = Note.get_signature(url=self.url, timestamp=self.timestamp)

@staticmethod
def get_signature(url, timestamp):
data = "{timestamp}?{url}".format(timestamp=timestamp, url=url)
sig = HMAC.new(dconfig.server_secret, data, SHA384)
return sig.hexdigest()

def exists(self):
"""Checks if note already exists"""
return os.path.exists(self.path())
Expand Down
4 changes: 3 additions & 1 deletion dnote/templates/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,9 @@ <h1>Self destructing encrypted notes</h1>
{% endif %}
</p>
{% endif %}
<input name="new_url" type="hidden" value="{{ random }}">
<input name="new_url" type="hidden" value="{{ note.url }}"/>
<input name="timestamp" type="hidden" value="{{ note.timestamp }}"/>
<input name="signature" type="hidden" value="{{ note.signature }}"/>
<p>Type or paste your text below:</p>
<textarea style="width:100%; height:250px" id="paste" name="paste"></textarea>
<br/>
Expand Down
7 changes: 7 additions & 0 deletions dnote/templates/signerror.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
{% extends "base.html" %}
{% block title %}Signature Error{% endblock %}
{% block content %}
<h1>Error</h1>
<p>Your request appears to have been tampered with. Please try again.</p>
<p>error: {{ error }}</p>
{% endblock %}
2 changes: 2 additions & 0 deletions scripts/generate_dnote_hashes
Original file line number Diff line number Diff line change
Expand Up @@ -45,4 +45,6 @@ if not os.path.exists(dconfig):
f.write('mac_salt = "%s"\n' % os.urandom(16).encode('hex'))
f.write('nonce_salt = "%s"\n' % os.urandom(16).encode('hex'))
f.write('duress_salt = "%s"\n' % os.urandom(16).encode('hex'))
f.write('server_secret = "%s"\n' % os.urandom(64).encode('hex'))
f.write('signature_validity = 300 # seconds\n')
os.chmod(dconfig, 0440)

0 comments on commit 71f0025

Please sign in to comment.