Adds blob encryption on both sides

This commit is contained in:
Sergi Delgado Segura
2019-06-05 16:44:43 +01:00
parent 40cdcfa913
commit fb2bf05057
10 changed files with 146 additions and 42 deletions

1
.gitignore vendored
View File

@@ -16,6 +16,7 @@ bitcoin.conf
*__pycache__ *__pycache__
.pending* .pending*
pisa.log pisa.log
pisa-btc/apps/*.json
.\#* .\#*
build/ build/

43
pisa-btc/apps/blob.py Normal file
View File

@@ -0,0 +1,43 @@
import hashlib
from binascii import hexlify
from cryptography.hazmat.primitives.ciphers.aead import AESGCM
SUPPORTED_HASH_FUNCTIONS = ['SHA256']
SUPPORTED_CYPHERS = ['AES-GCM-128']
class Blob:
def __init__(self, data, cypher, hash_function):
self.data = data
self.cypher = cypher
self.hash_function = hash_function
# FIXME: We only support SHA256 for now
if self.hash_function.upper() not in SUPPORTED_HASH_FUNCTIONS:
raise Exception('Hash function not supported ({}). Supported Hash functions: {}'
.format(self.hash_function, SUPPORTED_HASH_FUNCTIONS))
# FIXME: We only support SHA256 for now
if self.cypher.upper() not in SUPPORTED_CYPHERS:
raise Exception('Cypher not supported ({}). Supported cyphers: {}'.format(self.hash_function,
SUPPORTED_CYPHERS))
def encrypt(self, tx_id):
# Transaction to be encrypted
# FIXME: The blob data should contain more things that just the transaction. Leaving like this for now.
tx = self.data.encode()
# FIXME: tx_id should not be necessary (can be derived from tx SegWit-like). Passing it for now
# Extend the key using SHA256 as a KDF
tx_id = tx_id.encode()
extended_key = hashlib.sha256(tx_id[:16]).digest()
# The 16 MSB of the extended key will serve as the AES GCM 128 secret key. The 16 LSB will serve as the IV.
sk = extended_key[:16]
nonce = extended_key[16:]
# Encrypt the data
aesgcm = AESGCM(sk)
encrypted_blob = hexlify(aesgcm.encrypt(nonce=nonce, data=tx, associated_data=None)).decode()
return encrypted_blob

View File

@@ -1 +0,0 @@
wrong_txid = "You should provide the 16 MSB (in hex) of the txid you'd like to be monitored."

View File

@@ -1,16 +1,41 @@
from multiprocessing.connection import Client import requests
import re
import os
import json
from getopt import getopt from getopt import getopt
from sys import argv from sys import argv
import logging
from conf import CLIENT_LOG_FILE
from apps.blob import Blob
from apps import PISA_API_SERVER, PISA_API_PORT from apps import PISA_API_SERVER, PISA_API_PORT
import apps.messages as msg
import re
commands = ['add_appointment'] commands = ['add_appointment']
def build_appointment(tx, tx_id, start_block, end_block, dispute_delta):
locator = tx_id[:16]
cipher = "AES-GCM-128"
hash_function = "SHA256"
# FIXME: The blob data should contain more things that just the transaction. Leaving like this for now.
blob = Blob(tx, cipher, hash_function)
# FIXME: tx_id should not be necessary (can be derived from tx SegWit-like). Passing it for now
encrypted_blob = blob.encrypt(tx_id)
appointment = {"locator": locator, "start_block": start_block, "end_block": end_block,
"dispute_delta": dispute_delta, "encrypted_blob": encrypted_blob, "cipher": cipher, "hash_function":
hash_function}
return appointment
def check_txid_format(txid): def check_txid_format(txid):
if len(txid) != 32: if len(txid) != 64:
raise Exception("txid does not matches the expected size (16-byte / 32 hex chars). " + msg.wrong_txid) raise Exception("txid does not matches the expected size (32-byte / 64 hex chars).")
return re.search(r'^[0-9A-Fa-f]+$', txid) is not None return re.search(r'^[0-9A-Fa-f]+$', txid) is not None
@@ -25,6 +50,12 @@ def show_usage():
if __name__ == '__main__': if __name__ == '__main__':
opts, args = getopt(argv[1:], '', commands) opts, args = getopt(argv[1:], '', commands)
# Configure logging
logging.basicConfig(format='%(asctime)s %(message)s', level=logging.INFO, handlers=[
logging.FileHandler(CLIENT_LOG_FILE),
logging.StreamHandler()
])
# Get args # Get args
if len(args) > 0: if len(args) > 0:
command = args[0] command = args[0]
@@ -35,18 +66,25 @@ if __name__ == '__main__':
if command in commands: if command in commands:
if len(args) != 2: if len(args) != 2:
raise Exception("txid missing. " + msg.wrong_txid) raise Exception("Path to appointment_data.json missing.")
arg = args[1] if not os.path.isfile(args[1]):
valid_locator = check_txid_format(arg) raise Exception("Can't find file " + args[1])
appointment_data = json.load(open(args[1]))
valid_locator = check_txid_format(appointment_data.get('tx_id'))
if valid_locator: if valid_locator:
conn = Client((PISA_API_SERVER, PISA_API_PORT)) pisa_url = "http://{}:{}".format(PISA_API_SERVER, PISA_API_PORT)
appointment = build_appointment(appointment_data.get('tx'), appointment_data.get('tx_id'),
appointment_data.get('start_time'), appointment_data.get('end_time'),
appointment_data.get('dispute_delta'))
# Argv could be undefined, but we only have one command so it's safe for now r = requests.post(url=pisa_url, json=json.dumps(appointment))
conn.send((command, arg))
logging.info("[Client] {} (code: {})".format(r.text, r.status_code))
else: else:
raise ValueError("The provided locator is not valid. " + msg.wrong_txid) raise ValueError("The provided locator is not valid.")
else: else:
show_usage() show_usage()

View File

@@ -16,8 +16,7 @@ def add_appointment():
logging.info('[API] connection accepted from {}:{}'.format(remote_addr, remote_port)) logging.info('[API] connection accepted from {}:{}'.format(remote_addr, remote_port))
# Check content type once if properly defined # Check content type once if properly defined
# FIXME: Temporary patch until Paddy set's the client properly request_data = json.loads(request.get_json())
request_data = json.loads(request.form['data'])
appointment = inspector.inspect(request_data, debug) appointment = inspector.inspect(request_data, debug)
if appointment: if appointment:

View File

@@ -1,13 +1,15 @@
from pisa.encrypted_blob import EncryptedBlob
# Basic appointment structure # Basic appointment structure
class Appointment: class Appointment:
def __init__(self, locator, start_time, end_time, dispute_delta, encrypted_blob, cypher): def __init__(self, locator, start_time, end_time, dispute_delta, encrypted_blob, cipher, hash_function):
self.locator = locator self.locator = locator
self.start_time = start_time self.start_time = start_time
self.end_time = end_time self.end_time = end_time
self.dispute_delta = dispute_delta self.dispute_delta = dispute_delta
self.encrypted_blob = encrypted_blob self.encrypted_blob = EncryptedBlob(encrypted_blob)
self.cipher = cypher self.cipher = cipher
self.hash_function = hash_function

View File

@@ -0,0 +1,23 @@
import hashlib
from binascii import unhexlify
from cryptography.hazmat.primitives.ciphers.aead import AESGCM
class EncryptedBlob:
def __init__(self, data):
self.data = data
def decrypt(self, key):
# Extend the key using SHA256 as a KDF
extended_key = hashlib.sha256(key).digest()
# The 16 MSB of the extended key will serve as the AES GCM 128 secret key. The 16 LSB will serve as the IV.
sk = extended_key[:16]
nonce = extended_key[16:]
# Decrypt
aesgcm = AESGCM(sk)
data = unhexlify(self.data.encode)
raw_tx = aesgcm.decrypt(nonce=nonce, data=data, associated_data=None)
return raw_tx

View File

@@ -10,41 +10,41 @@ class Inspector:
appointment = None appointment = None
locator = data.get('txlocator') locator = data.get('locator')
start_time = data.get('startblock') start_time = data.get('start_block')
end_time = data.get('endblock') end_time = data.get('end_block')
# Missing for now
dispute_delta = data.get('dispute_delta') dispute_delta = data.get('dispute_delta')
encrypted_blob = data.get('encrypted_blob')
# FIXME: this will be eventually be replaced, here for testing now
encrypted_blob = data.get('rawtx')
# encrypted_blob = data.get('encrypted_blob')
cipher = data.get('cipher') cipher = data.get('cipher')
hash_function = data.get('hash_function')
if self.check_locator(locator, debug) and self.check_start_time(start_time, debug) and \ if self.check_locator(locator, debug) and self.check_start_time(start_time, debug) and \
self.check_end_time(end_time, debug) and self.check_delta(dispute_delta, debug) and \ self.check_end_time(end_time, debug) and self.check_delta(dispute_delta, debug) and \
self.check_blob(encrypted_blob, debug) and self.check_cipher(cipher, debug): self.check_blob(encrypted_blob, debug) and self.check_cipher(cipher, debug) and \
appointment = Appointment(locator, start_time, end_time, dispute_delta, encrypted_blob, cipher) self.check_cipher(hash_function, debug):
appointment = Appointment(locator, start_time, end_time, dispute_delta, encrypted_blob, cipher,
hash_function)
return appointment return appointment
# FIXME: Define checks # FIXME: Define checks
def check_locator(self, locator, debug): def check_locator(self, locator, debug):
return True return locator is not None
def check_start_time(self, start_time, debug): def check_start_time(self, start_time, debug):
return True return start_time is not None
def check_end_time(self, end_time, debug): def check_end_time(self, end_time, debug):
return True return end_time is not None
def check_delta(self, dispute_delta, debug): def check_delta(self, dispute_delta, debug):
return True return dispute_delta is not None
def check_blob(self, encrypted_blob, debug): def check_blob(self, encrypted_blob, debug):
return True return encrypted_blob is not None
def check_cipher(self, cipher, debug): def check_cipher(self, cipher, debug):
return True return cipher is not None
def check_hash_function(self, hash_function, debug):
return hash_function is not None

View File

@@ -1,7 +1,7 @@
import logging import logging
from sys import argv from sys import argv
from getopt import getopt from getopt import getopt
from conf import LOG_FILE from conf import SERVER_LOG_FILE
from threading import Thread from threading import Thread
from pisa.api import start_api from pisa.api import start_api
@@ -15,7 +15,7 @@ if __name__ == '__main__':
# Configure logging # Configure logging
logging.basicConfig(format='%(asctime)s %(message)s', level=logging.INFO, handlers=[ logging.basicConfig(format='%(asctime)s %(message)s', level=logging.INFO, handlers=[
logging.FileHandler(LOG_FILE), logging.FileHandler(SERVER_LOG_FILE),
logging.StreamHandler() logging.StreamHandler()
]) ])

View File

@@ -121,10 +121,9 @@ class Watcher:
for appointment_pos, appointment in enumerate(self.appointments.get(locator)): for appointment_pos, appointment in enumerate(self.appointments.get(locator)):
try: try:
dispute_txid = locator + k dispute_txid = locator + k
rawtx = decrypt_tx(appointment.encrypted_blob, k, appointment.cipher) raw_tx = appointment.encrypted_blob.decrypt(k)
txid = bitcoin_cli.decoderawtransaction(raw_tx).get('txid')
txid = bitcoin_cli.decoderawtransaction(rawtx).get('txid') matches.append((locator, appointment_pos, dispute_txid, txid, raw_tx))
matches.append((locator, appointment_pos, dispute_txid, txid, rawtx))
if debug: if debug:
logging.error("[Watcher] match found for {}:{}! {}".format(locator, appointment_pos, logging.error("[Watcher] match found for {}:{}! {}".format(locator, appointment_pos,