Skip to content

Commit 296e4aa

Browse files
authored
Fix sslcrypto thread safety (#2454)
* Use sslcrypto instead of pyelliptic and pybitcointools * Fix CryptMessage * Support Python 3.4 * Fix user creation * Get rid of pyelliptic and pybitcointools * Fix typo * Delete test file * Add sslcrypto to tree * Update sslcrypto * Add pyaes to src/lib * Fix typo in tests * Update sslcrypto version * Use privatekey_bin instead of privatekey for bytes objects * Fix sslcrypto * Fix Benchmark plugin * Don't calculate the same thing twice * Only import sslcrypto once * Handle fallback sslcrypto implementation during tests * Fix sslcrypto fallback implementation selection * Fix thread safety * Add derivation * Bring split back * Fix typo * v3.3 * Fix custom OpenSSL discovery
1 parent 7ba2c93 commit 296e4aa

57 files changed

Lines changed: 3781 additions & 7306 deletions

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

.gitlab-ci.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@ stages:
2121
- python -m pytest -x plugins/Multiuser/Test --color=yes
2222
- mv plugins/disabled-Bootstrapper plugins/Bootstrapper
2323
- python -m pytest -x plugins/Bootstrapper/Test --color=yes
24-
- flake8 . --count --select=E9,F63,F72,F82 --show-source --statistics --exclude=src/lib/pybitcointools/
24+
- flake8 . --count --select=E9,F63,F72,F82 --show-source --statistics --exclude=src/lib/pyaes/
2525

2626
test:py3.4:
2727
image: python:3.4.3

.travis.yml

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -33,10 +33,13 @@ script:
3333
- export ZERONET_LOG_DIR="log/Bootstrapper"; mv plugins/disabled-Bootstrapper plugins/Bootstrapper && catchsegv python -m pytest -x plugins/Bootstrapper/Test
3434
- find src -name "*.json" | xargs -n 1 python3 -c "import json, sys; print(sys.argv[1], end=' '); json.load(open(sys.argv[1])); print('[OK]')"
3535
- find plugins -name "*.json" | xargs -n 1 python3 -c "import json, sys; print(sys.argv[1], end=' '); json.load(open(sys.argv[1])); print('[OK]')"
36-
- flake8 . --count --select=E9,F63,F72,F82 --show-source --statistics --exclude=src/lib/pybitcointools/
36+
- flake8 . --count --select=E9,F63,F72,F82 --show-source --statistics --exclude=src/lib/pyaes/
3737
after_failure:
3838
- zip -r log.zip log/
3939
- curl --upload-file ./log.zip https://transfer.sh/log.zip
40+
after_success:
41+
- codecov
42+
- coveralls --rcfile=src/Test/coverage.ini
4043
notifications:
4144
email:
4245
recipients:

plugins/Benchmark/BenchmarkPlugin.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -104,8 +104,8 @@ def getBenchmarkTests(self, online=False):
104104
tests.extend([
105105
{"func": self.testHdPrivatekey, "num": 50, "time_standard": 0.57},
106106
{"func": self.testSign, "num": 20, "time_standard": 0.46},
107-
{"func": self.testVerify, "kwargs": {"lib_verify": "btctools"}, "num": 20, "time_standard": 0.38},
108-
{"func": self.testVerify, "kwargs": {"lib_verify": "openssl"}, "num": 200, "time_standard": 0.30},
107+
{"func": self.testVerify, "kwargs": {"lib_verify": "sslcrypto_fallback"}, "num": 20, "time_standard": 0.38},
108+
{"func": self.testVerify, "kwargs": {"lib_verify": "sslcrypto"}, "num": 200, "time_standard": 0.30},
109109
{"func": self.testVerify, "kwargs": {"lib_verify": "libsecp256k1"}, "num": 200, "time_standard": 0.10},
110110

111111
{"func": self.testPackMsgpack, "num": 100, "time_standard": 0.35},
Lines changed: 14 additions & 47 deletions
Original file line numberDiff line numberDiff line change
@@ -1,28 +1,22 @@
11
import hashlib
22
import base64
33
import struct
4-
5-
import lib.pybitcointools as btctools
4+
from lib import sslcrypto
65
from Crypt import Crypt
76

8-
ecc_cache = {}
7+
8+
curve = sslcrypto.ecc.get_curve("secp256k1")
99

1010

11-
def eciesEncrypt(data, pubkey, ephemcurve=None, ciphername='aes-256-cbc'):
12-
from lib import pyelliptic
13-
pubkey_openssl = toOpensslPublickey(base64.b64decode(pubkey))
14-
curve, pubkey_x, pubkey_y, i = pyelliptic.ECC._decode_pubkey(pubkey_openssl)
15-
if ephemcurve is None:
16-
ephemcurve = curve
17-
ephem = pyelliptic.ECC(curve=ephemcurve)
18-
key = hashlib.sha512(ephem.raw_get_ecdh_key(pubkey_x, pubkey_y)).digest()
19-
key_e, key_m = key[:32], key[32:]
20-
pubkey = ephem.get_pubkey()
21-
iv = pyelliptic.OpenSSL.rand(pyelliptic.OpenSSL.get_cipher(ciphername).get_blocksize())
22-
ctx = pyelliptic.Cipher(key_e, iv, 1, ciphername)
23-
ciphertext = iv + pubkey + ctx.ciphering(data)
24-
mac = pyelliptic.hmac_sha256(key_m, ciphertext)
25-
return key_e, ciphertext + mac
11+
def eciesEncrypt(data, pubkey, ciphername="aes-256-cbc"):
12+
ciphertext, key_e = curve.encrypt(
13+
data,
14+
base64.b64decode(pubkey),
15+
algo=ciphername,
16+
derivation="sha512",
17+
return_aes_key=True
18+
)
19+
return key_e, ciphertext
2620

2721

2822
@Crypt.thread_pool_crypt.wrap
@@ -37,9 +31,8 @@ def eciesDecryptMulti(encrypted_datas, privatekey):
3731
return texts
3832

3933

40-
def eciesDecrypt(encrypted_data, privatekey):
41-
ecc_key = getEcc(privatekey)
42-
return ecc_key.decrypt(base64.b64decode(encrypted_data))
34+
def eciesDecrypt(ciphertext, privatekey):
35+
return curve.decrypt(base64.b64decode(ciphertext), curve.wif_to_private(privatekey), derivation="sha512")
4336

4437

4538
def decodePubkey(pubkey):
@@ -63,29 +56,3 @@ def split(encrypted):
6356
ciphertext = encrypted[16 + i:-32]
6457

6558
return iv, ciphertext
66-
67-
68-
def getEcc(privatekey=None):
69-
from lib import pyelliptic
70-
global ecc_cache
71-
if privatekey not in ecc_cache:
72-
if privatekey:
73-
publickey_bin = btctools.encode_pubkey(btctools.privtopub(privatekey), "bin")
74-
publickey_openssl = toOpensslPublickey(publickey_bin)
75-
privatekey_openssl = toOpensslPrivatekey(privatekey)
76-
ecc_cache[privatekey] = pyelliptic.ECC(curve='secp256k1', privkey=privatekey_openssl, pubkey=publickey_openssl)
77-
else:
78-
ecc_cache[None] = pyelliptic.ECC()
79-
return ecc_cache[privatekey]
80-
81-
82-
def toOpensslPrivatekey(privatekey):
83-
privatekey_bin = btctools.encode_privkey(privatekey, "bin")
84-
return b'\x02\xca\x00\x20' + privatekey_bin
85-
86-
87-
def toOpensslPublickey(publickey):
88-
publickey_bin = btctools.encode_pubkey(publickey, "bin")
89-
publickey_bin = publickey_bin[1:]
90-
publickey_openssl = b'\x02\xca\x00 ' + publickey_bin[:32] + b'\x00 ' + publickey_bin[32:]
91-
return publickey_openssl

plugins/CryptMessage/CryptMessagePlugin.py

Lines changed: 18 additions & 38 deletions
Original file line numberDiff line numberDiff line change
@@ -5,24 +5,22 @@
55

66
from Plugin import PluginManager
77
from Crypt import CryptBitcoin, CryptHash
8-
import lib.pybitcointools as btctools
98
from Config import config
9+
import sslcrypto
10+
1011
from . import CryptMessage
1112

13+
curve = sslcrypto.ecc.get_curve("secp256k1")
14+
1215

1316
@PluginManager.registerTo("UiWebsocket")
1417
class UiWebsocketPlugin(object):
15-
def eciesDecrypt(self, encrypted, privatekey):
16-
back = CryptMessage.getEcc(privatekey).decrypt(encrypted)
17-
return back.decode("utf8")
18-
1918
# - Actions -
2019

2120
# Returns user's public key unique to site
2221
# Return: Public key
2322
def actionUserPublickey(self, to, index=0):
24-
publickey = self.user.getEncryptPublickey(self.site.address, index)
25-
self.response(to, publickey)
23+
self.response(to, self.user.getEncryptPublickey(self.site.address, index))
2624

2725
# Encrypt a text using the publickey or user's sites unique publickey
2826
# Return: Encrypted text using base64 encoding
@@ -55,32 +53,23 @@ def actionEciesDecrypt(self, to, param, privatekey=0):
5553

5654
# Encrypt a text using AES
5755
# Return: Iv, AES key, Encrypted text
58-
def actionAesEncrypt(self, to, text, key=None, iv=None):
59-
from lib import pyelliptic
60-
56+
def actionAesEncrypt(self, to, text, key=None):
6157
if key:
6258
key = base64.b64decode(key)
6359
else:
64-
key = os.urandom(32)
65-
66-
if iv: # Generate new AES key if not definied
67-
iv = base64.b64decode(iv)
68-
else:
69-
iv = pyelliptic.Cipher.gen_IV('aes-256-cbc')
60+
key = sslcrypto.aes.new_key()
7061

7162
if text:
72-
encrypted = pyelliptic.Cipher(key, iv, 1, ciphername='aes-256-cbc').ciphering(text.encode("utf8"))
63+
encrypted, iv = sslcrypto.aes.encrypt(text.encode("utf8"), key)
7364
else:
74-
encrypted = b""
65+
encrypted, iv = b"", b""
7566

7667
res = [base64.b64encode(item).decode("utf8") for item in [key, iv, encrypted]]
7768
self.response(to, res)
7869

7970
# Decrypt a text using AES
8071
# Return: Decrypted text
8172
def actionAesDecrypt(self, to, *args):
82-
from lib import pyelliptic
83-
8473
if len(args) == 3: # Single decrypt
8574
encrypted_texts = [(args[0], args[1])]
8675
keys = [args[2]]
@@ -93,9 +82,8 @@ def actionAesDecrypt(self, to, *args):
9382
iv = base64.b64decode(iv)
9483
text = None
9584
for key in keys:
96-
ctx = pyelliptic.Cipher(base64.b64decode(key), iv, 0, ciphername='aes-256-cbc')
9785
try:
98-
decrypted = ctx.ciphering(encrypted_text)
86+
decrypted = sslcrypto.aes.decrypt(encrypted_text, iv, base64.b64decode(key))
9987
if decrypted and decrypted.decode("utf8"): # Valid text decoded
10088
text = decrypted.decode("utf8")
10189
except Exception as err:
@@ -122,12 +110,11 @@ def actionEcdsaVerify(self, to, data, address, signature):
122110

123111
# Gets the publickey of a given privatekey
124112
def actionEccPrivToPub(self, to, privatekey):
125-
self.response(to, btctools.privtopub(privatekey))
113+
self.response(to, curve.private_to_public(curve.wif_to_private(privatekey)))
126114

127115
# Gets the address of a given publickey
128116
def actionEccPubToAddr(self, to, publickey):
129-
address = btctools.pubtoaddr(btctools.decode_pubkey(publickey))
130-
self.response(to, address)
117+
self.response(to, curve.public_to_address(bytes.fromhex(publickey)))
131118

132119

133120
@PluginManager.registerTo("User")
@@ -163,7 +150,7 @@ def getEncryptPublickey(self, address, param_index=0):
163150

164151
if "encrypt_publickey_%s" % index not in site_data:
165152
privatekey = self.getEncryptPrivatekey(address, param_index)
166-
publickey = btctools.encode_pubkey(btctools.privtopub(privatekey), "bin_compressed")
153+
publickey = curve.private_to_public(curve.wif_to_private(privatekey))
167154
site_data["encrypt_publickey_%s" % index] = base64.b64encode(publickey).decode("utf8")
168155
return site_data["encrypt_publickey_%s" % index]
169156

@@ -200,8 +187,8 @@ def testCryptEciesDecrypt(self, num_run=1):
200187
aes_key, encrypted = CryptMessage.eciesEncrypt(self.utf8_text.encode("utf8"), self.publickey)
201188
for i in range(num_run):
202189
assert len(aes_key) == 32
203-
ecc = CryptMessage.getEcc(self.privatekey)
204-
assert ecc.decrypt(encrypted) == self.utf8_text.encode("utf8"), "%s != %s" % (ecc.decrypt(encrypted), self.utf8_text.encode("utf8"))
190+
decrypted = CryptMessage.eciesDecrypt(base64.b64encode(encrypted), self.privatekey)
191+
assert decrypted == self.utf8_text.encode("utf8"), "%s != %s" % (decrypted, self.utf8_text.encode("utf8"))
205192
yield "."
206193

207194
def testCryptEciesDecryptMulti(self, num_run=1):
@@ -223,23 +210,16 @@ def testCryptEciesDecryptMulti(self, num_run=1):
223210
gevent.joinall(threads)
224211

225212
def testCryptAesEncrypt(self, num_run=1):
226-
from lib import pyelliptic
227-
228213
for i in range(num_run):
229214
key = os.urandom(32)
230-
iv = pyelliptic.Cipher.gen_IV('aes-256-cbc')
231-
encrypted = pyelliptic.Cipher(key, iv, 1, ciphername='aes-256-cbc').ciphering(self.utf8_text.encode("utf8"))
215+
encrypted = sslcrypto.aes.encrypt(self.utf8_text.encode("utf8"), key)
232216
yield "."
233217

234218
def testCryptAesDecrypt(self, num_run=1):
235-
from lib import pyelliptic
236-
237219
key = os.urandom(32)
238-
iv = pyelliptic.Cipher.gen_IV('aes-256-cbc')
239-
encrypted_text = pyelliptic.Cipher(key, iv, 1, ciphername='aes-256-cbc').ciphering(self.utf8_text.encode("utf8"))
220+
encrypted_text, iv = sslcrypto.aes.encrypt(self.utf8_text.encode("utf8"), key)
240221

241222
for i in range(num_run):
242-
ctx = pyelliptic.Cipher(key, iv, 0, ciphername='aes-256-cbc')
243-
decrypted = ctx.ciphering(encrypted_text).decode("utf8")
223+
decrypted = sslcrypto.aes.decrypt(encrypted_text, iv, key).decode("utf8")
244224
assert decrypted == self.utf8_text
245225
yield "."

plugins/CryptMessage/Test/TestCrypt.py

Lines changed: 2 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -18,13 +18,10 @@ def testEncryptEcies(self, text, text_repeat):
1818
assert len(aes_key) == 32
1919
# assert len(encrypted) == 134 + int(len(text) / 16) * 16 # Not always true
2020

21-
ecc = CryptMessage.getEcc(self.privatekey)
22-
assert ecc.decrypt(encrypted) == text_repeated
21+
assert CryptMessage.eciesDecrypt(base64.b64encode(encrypted), self.privatekey) == text_repeated
2322

2423
def testDecryptEcies(self, user):
25-
encrypted = base64.b64decode(self.ecies_encrypted_text)
26-
ecc = CryptMessage.getEcc(self.privatekey)
27-
assert ecc.decrypt(encrypted) == b"hello"
24+
assert CryptMessage.eciesDecrypt(self.ecies_encrypted_text, self.privatekey) == b"hello"
2825

2926
def testPublickey(self, ui_websocket):
3027
pub = ui_websocket.testAction("UserPublickey", 0)

requirements.txt

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,5 +9,4 @@ pyasn1
99
websocket_client
1010
gevent-ws
1111
coincurve
12-
python-bitcoinlib
1312
maxminddb

0 commit comments

Comments
 (0)