diff --git a/dnote/__init__.py b/dnote/__init__.py index b2b6758..1dc7433 100644 --- a/dnote/__init__.py +++ b/dnote/__init__.py @@ -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(): @@ -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'] @@ -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() diff --git a/dnote/note.py b/dnote/note.py index 89a63a3..1c9cb1c 100644 --- a/dnote/note.py +++ b/dnote/note.py @@ -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 @@ -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()) diff --git a/dnote/templates/index.html b/dnote/templates/index.html index 2183bc4..004d1e5 100644 --- a/dnote/templates/index.html +++ b/dnote/templates/index.html @@ -19,7 +19,9 @@
Type or paste your text below:
Your request appears to have been tampered with. Please try again.
+error: {{ error }}
+{% endblock %} diff --git a/scripts/generate_dnote_hashes b/scripts/generate_dnote_hashes index eb6f2aa..d7303e6 100755 --- a/scripts/generate_dnote_hashes +++ b/scripts/generate_dnote_hashes @@ -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)