fix(model/paste): interpolate expiration
paste model now consists of expiration instead of lifetime and timestamp. This is to ensure, that man-in-the-middle attackers cannot derive the origin of a paste through observing transport layer server request times.
This commit is contained in:
parent
bcfab0fbad
commit
f0c0188d58
7 changed files with 50 additions and 44 deletions
|
|
@ -26,7 +26,7 @@ class SQLite(Backend):
|
|||
|
||||
def __init__(self, parameters: SqliteParameters):
|
||||
|
||||
parameters['connection'] = get_sqlite_connection(parameters)
|
||||
parameters = SqliteParameters(parameters.path, get_sqlite_connection(parameters))
|
||||
|
||||
self.user = SqliteUser(parameters, User)
|
||||
self.paste = SqlitePaste(parameters, Paste)
|
||||
|
|
|
|||
|
|
@ -7,6 +7,15 @@ from pathlib import Path
|
|||
from ast import literal_eval
|
||||
|
||||
|
||||
COLUMNS = [
|
||||
'data',
|
||||
'data_hash',
|
||||
'sub',
|
||||
'expiration',
|
||||
'encoding'
|
||||
]
|
||||
|
||||
|
||||
def load(
|
||||
proto: object,
|
||||
path: Path,
|
||||
|
|
@ -22,13 +31,7 @@ def load(
|
|||
return None
|
||||
|
||||
cells = {}
|
||||
for column in [
|
||||
'data',
|
||||
'data_hash',
|
||||
'sub',
|
||||
'timestamp',
|
||||
'lifetime',
|
||||
'encoding']:
|
||||
for column in COLUMNS:
|
||||
|
||||
cell = row.joinpath(column)
|
||||
|
||||
|
|
@ -56,8 +59,7 @@ def load(
|
|||
cells['sub'],
|
||||
cells['data'],
|
||||
cells['data_hash'],
|
||||
cells['timestamp'],
|
||||
cells['lifetime'],
|
||||
cells['expiration'],
|
||||
cells['encoding'])
|
||||
|
||||
|
||||
|
|
@ -68,13 +70,7 @@ def dump(model: object, path: Path, model_schema: type) -> None:
|
|||
row = path.joinpath(model.pid.hex())
|
||||
row.mkdir(parents=True, exist_ok=True)
|
||||
|
||||
for column in [
|
||||
'data',
|
||||
'data_hash',
|
||||
'sub',
|
||||
'timestamp',
|
||||
'lifetime',
|
||||
'encoding']:
|
||||
for column in COLUMNS:
|
||||
|
||||
cell = row.joinpath(column)
|
||||
cell_schema = getattr(model_schema, column)
|
||||
|
|
|
|||
|
|
@ -11,7 +11,7 @@ def load(proto: object, connection: Connection, model_class: type):
|
|||
cur = connection.cursor()
|
||||
|
||||
cur.execute(
|
||||
'SELECT pid, data, data_hash, sub, timestamp, lifetime, encoding FROM pastes WHERE pid=?',
|
||||
'SELECT pid, data, data_hash, sub, expiration, encoding FROM pastes WHERE pid=?',
|
||||
(proto.pid,
|
||||
))
|
||||
|
||||
|
|
@ -24,8 +24,7 @@ def load(proto: object, connection: Connection, model_class: type):
|
|||
result['sub'],
|
||||
result['data'],
|
||||
result['data_hash'],
|
||||
result['timestamp'],
|
||||
result['lifetime'],
|
||||
result['expiration'],
|
||||
result['encoding'])
|
||||
|
||||
return None
|
||||
|
|
@ -38,14 +37,13 @@ def dump(model: object, connection: Connection):
|
|||
cur = connection.cursor()
|
||||
|
||||
cur.execute(
|
||||
'''INSERT INTO pastes (pid, data, data_hash, sub, timestamp, lifetime, encoding)
|
||||
VALUES (?,?,?,?,?,?,?)''',
|
||||
'''INSERT INTO pastes (pid, data, data_hash, sub, expiration, encoding)
|
||||
VALUES (?,?,?,?,?,?)''',
|
||||
(model.pid,
|
||||
model.data,
|
||||
model.data_hash,
|
||||
model.sub,
|
||||
model.timestamp,
|
||||
model.lifetime,
|
||||
model.expiration,
|
||||
model.encoding))
|
||||
|
||||
connection.commit()
|
||||
|
|
|
|||
|
|
@ -1,10 +1,9 @@
|
|||
CREATE TABLE IF NOT EXISTS "pastes" (
|
||||
"id" BLOB NOT NULL UNIQUE,
|
||||
"pid" BLOB NOT NULL UNIQUE,
|
||||
"data" BLOB NOT NULL,
|
||||
"data_hash" BLOB NOT NULL,
|
||||
"sub" BLOB UNIQUE,
|
||||
"timestamp" INTEGER NOT NULL,
|
||||
"lifetime" INTEGER NOT NULL,
|
||||
"encoding" TEXT
|
||||
PRIMARY KEY("id")
|
||||
"expiration" INTEGER NOT NULL,
|
||||
"encoding" TEXT,
|
||||
PRIMARY KEY("pid")
|
||||
);
|
||||
|
|
@ -71,7 +71,7 @@ def get(**kwargs):
|
|||
config.salt, config.hmac_iterations)
|
||||
|
||||
try:
|
||||
data, lifetime, encoding = call()
|
||||
data, expiration, encoding = call()
|
||||
except paste_model.LifetimeError as e:
|
||||
if kwargs.get('user') is not None:
|
||||
paste_model.remove_safe(pid, sub, pkey, config.backend.paste,
|
||||
|
|
@ -85,7 +85,7 @@ def get(**kwargs):
|
|||
raise ForbiddenError(str(e))
|
||||
|
||||
# burn after read
|
||||
if lifetime < 0:
|
||||
if expiration < 0:
|
||||
if kwargs.get('user') is not None:
|
||||
paste_model.remove_safe(pid, sub, pkey, config.backend.paste,
|
||||
config.salt, config.hmac_iterations)
|
||||
|
|
|
|||
|
|
@ -12,6 +12,7 @@ class PasteDataSchema:
|
|||
sub = bytes
|
||||
timestamp = int
|
||||
lifetime = int
|
||||
expiration = int
|
||||
encoding = str
|
||||
|
||||
|
||||
|
|
@ -54,6 +55,14 @@ class PasteEncoding(PasteDataSchema.encoding):
|
|||
"""
|
||||
|
||||
|
||||
class PasteExpiration(PasteDataSchema.expiration):
|
||||
"""Paste Expiration
|
||||
|
||||
< 0: after first acccess
|
||||
0: never
|
||||
"""
|
||||
|
||||
|
||||
class PasteLifetime(PasteDataSchema.lifetime):
|
||||
"""Paste Lifetime
|
||||
"""
|
||||
|
|
@ -128,8 +137,6 @@ class Paste(NamedTuple):
|
|||
#: paste data hash
|
||||
data_hash: Optional[PasteHash] = None
|
||||
#: paste timestamp
|
||||
timestamp: Optional[PasteTimestamp] = None
|
||||
#: paste lifetime
|
||||
lifetime: Optional[PasteLifetime] = None
|
||||
expiration: Optional[PasteExpiration] = None
|
||||
#: paste encoding
|
||||
encoding: Optional[PasteEncoding] = None
|
||||
|
|
|
|||
|
|
@ -10,7 +10,7 @@ from httpaste.helper.crypto import dhash, shash, encrypt, decrypt
|
|||
from httpaste.helper.common import generate_random_string
|
||||
from httpaste.model import (Paste, PasteId, Sub, MasterKey, PasteKey, Salt,
|
||||
PasteData, PasteHash, PasteTimestamp, PasteSub,
|
||||
PasteLifetime, PasteEncoding)
|
||||
PasteLifetime, PasteEncoding, PasteExpiration)
|
||||
|
||||
|
||||
class NotFoundError(Exception):
|
||||
|
|
@ -87,8 +87,7 @@ def load(proto: Paste, backend: object) -> Optional[Paste]:
|
|||
|
||||
raise SubError('Paste not owned by user')
|
||||
|
||||
if model.lifetime >= 0 and model.timestamp + \
|
||||
(60 * model.lifetime) < int(time.time()):
|
||||
if model.expiration > 0 and model.expiration < int(time.time()):
|
||||
|
||||
raise LifetimeError('Paste expired')
|
||||
|
||||
|
|
@ -121,8 +120,7 @@ def load_safe(
|
|||
proto.sub,
|
||||
data,
|
||||
model.data_hash,
|
||||
model.timestamp,
|
||||
model.lifetime,
|
||||
model.expiration,
|
||||
model.encoding)
|
||||
|
||||
|
||||
|
|
@ -195,6 +193,11 @@ def create(
|
|||
sub = None
|
||||
timestamp = PasteTimestamp(int(time.time()))
|
||||
|
||||
if lifetime < 0:
|
||||
expiration = -1
|
||||
else:
|
||||
expiration = PasteExpiration(timestamp + (lifetime * 60))
|
||||
|
||||
safe_data = PasteData(encrypt(data, pid, salt, hmac_iter))
|
||||
|
||||
model = Paste(
|
||||
|
|
@ -202,8 +205,7 @@ def create(
|
|||
sub,
|
||||
safe_data,
|
||||
data_hash,
|
||||
timestamp,
|
||||
lifetime,
|
||||
expiration,
|
||||
encoding)
|
||||
|
||||
dump(model, backend)
|
||||
|
|
@ -234,6 +236,11 @@ def create_safe(data: PasteData,
|
|||
safe_sub = PasteSub(shash(sub, data_hash, pid))
|
||||
timestamp = PasteTimestamp(int(time.time()))
|
||||
|
||||
if lifetime < 0:
|
||||
expiration = -1
|
||||
else:
|
||||
expiration = PasteExpiration(timestamp + (lifetime * 60))
|
||||
|
||||
safe_data = PasteData(encrypt(data, pkey, salt, hmac_iter))
|
||||
|
||||
dump(Paste(
|
||||
|
|
@ -241,8 +248,7 @@ def create_safe(data: PasteData,
|
|||
safe_sub,
|
||||
safe_data,
|
||||
data_hash,
|
||||
timestamp,
|
||||
lifetime,
|
||||
expiration,
|
||||
encoding
|
||||
), backend)
|
||||
|
||||
|
|
@ -279,7 +285,7 @@ def get(pid: PasteId, backend: object, salt: Salt = Config.salt, hmac_iter: int
|
|||
|
||||
data = decrypt(model.data, pid, salt, hmac_iter)
|
||||
|
||||
return PasteData(data), model.lifetime, model.encoding
|
||||
return PasteData(data), model.expiration, model.encoding
|
||||
|
||||
|
||||
def get_safe(
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue