Skip to content

Commit

Permalink
Merge remote-tracking branch 'upstream/master' into develop
Browse files Browse the repository at this point in the history
  • Loading branch information
wakiyamap committed Jan 7, 2018
2 parents 62264af + f8fad2f commit 0bff3e4
Show file tree
Hide file tree
Showing 10 changed files with 81 additions and 38 deletions.
10 changes: 10 additions & 0 deletions RELEASE-NOTES
Original file line number Diff line number Diff line change
@@ -1,3 +1,13 @@
# Release 3.0.4 : (Security update)

* Fix a vulnerability caused by Cross-Origin Resource Sharing (CORS)
in the JSONRPC interface. Previous versions of Electrum are
vulnerable to port scanning and deanonimization attacks from
malicious websites. Wallets that are not password-protected are
vulnerable to theft.
* Bundle QR scanner with Android app
* Minor bug fixes

# Release 3.0.3
* Qt GUI: sweeping now uses the Send tab, allowing fees to be set
* Windows: if using the installer binary, there is now a separate shortcut
Expand Down
18 changes: 10 additions & 8 deletions gui/qt/main_window.py
Original file line number Diff line number Diff line change
Expand Up @@ -2016,24 +2016,26 @@ def show_private_key(self, address, password):
d.setLayout(vbox)
d.exec_()

msg_sign = ("Signing with an address actually means signing with the corresponding "
msg_sign = _("Signing with an address actually means signing with the corresponding "
"private key, and verifying with the corresponding public key. The "
"address you have entered does not have a unique public key, so these "
"operations cannot be performed.")
"operations cannot be performed.") + '\n\n' + \
_('The operation is undefined. Not just in Electrum, but in general.')

@protected
def do_sign(self, address, message, signature, password):
address = address.text().strip()
message = message.toPlainText().strip()
if not bitcoin.is_address(address):
self.show_message('Invalid Bitcoin address.')
self.show_message(_('Invalid Bitcoin address.'))
return
if not self.wallet.is_mine(address):
self.show_message(_('Address not in wallet.'))
return
txin_type = self.wallet.get_txin_type(address)
if txin_type not in ['p2pkh', 'p2wpkh', 'p2wpkh-p2sh']:
self.show_message('Cannot sign messages with this type of address.' + '\n\n' + self.msg_sign)
return
if not self.wallet.is_mine(address):
self.show_message('Address not in wallet.')
self.show_message(_('Cannot sign messages with this type of address:') + \
' ' + txin_type + '\n\n' + self.msg_sign)
return
task = partial(self.wallet.sign_message, address, message, password)

Expand All @@ -2045,7 +2047,7 @@ def do_verify(self, address, message, signature):
address = address.text().strip()
message = message.toPlainText().strip().encode('utf-8')
if not bitcoin.is_address(address):
self.show_message('Invalid Bitcoin address.')
self.show_message(_('Invalid Bitcoin address.'))
return
try:
# This can throw on invalid base64
Expand Down
10 changes: 5 additions & 5 deletions lib/base_wizard.py
Original file line number Diff line number Diff line change
Expand Up @@ -294,7 +294,7 @@ def on_restore_seed(self, seed, is_bip39, is_ext):
self.run('create_keystore', seed, '')
elif self.seed_type == '2fa':
if self.is_kivy:
self.show_error('2FA seeds are not supported in this version')
self.show_error(_('2FA seeds are not supported in this version'))
self.run('restore_from_seed')
else:
self.load_2fa()
Expand Down Expand Up @@ -386,10 +386,10 @@ def on_cosigner(self, text, password, i):
def choose_seed_type(self):
title = _('Choose Seed type')
message = ' '.join([
"The type of addresses used by your wallet will depend on your seed.",
"Segwit wallets use bech32 addresses, defined in BIP173.",
"Please note that websites and other wallets may not support these addresses yet.",
"Thus, you might want to keep using a non-segwit wallet in order to be able to receive bitcoins during the transition period."
_("The type of addresses used by your wallet will depend on your seed."),
_("Segwit wallets use bech32 addresses, defined in BIP173."),
_("Please note that websites and other wallets may not support these addresses yet."),
_("Thus, you might want to keep using a non-segwit wallet in order to be able to receive bitcoins during the transition period.")
])
choices = [
('create_standard_seed', _('Standard')),
Expand Down
19 changes: 3 additions & 16 deletions lib/daemon.py
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@

# from jsonrpc import JSONRPCResponseManager
import jsonrpclib
from jsonrpclib.SimpleJSONRPCServer import SimpleJSONRPCServer, SimpleJSONRPCRequestHandler
from jsonrpclib.SimpleJSONRPCServer import SimpleJSONRPCServer

from .version import ELECTRUM_VERSION
from .network import Network
Expand Down Expand Up @@ -58,7 +58,7 @@ def get_fd_or_server(config):
lockfile = get_lockfile(config)
while True:
try:
return os.open(lockfile, os.O_CREAT | os.O_EXCL | os.O_WRONLY), None
return os.open(lockfile, os.O_CREAT | os.O_EXCL | os.O_WRONLY, 0o644), None
except OSError:
pass
server = get_server(config)
Expand Down Expand Up @@ -87,19 +87,6 @@ def get_server(config):
time.sleep(1.0)


class RequestHandler(SimpleJSONRPCRequestHandler):

def do_OPTIONS(self):
self.send_response(200)
self.end_headers()

def end_headers(self):
self.send_header("Access-Control-Allow-Headers",
"Origin, X-Requested-With, Content-Type, Accept")
self.send_header("Access-Control-Allow-Origin", "*")
SimpleJSONRPCRequestHandler.end_headers(self)


class Daemon(DaemonThread):

def __init__(self, config, fd):
Expand All @@ -124,7 +111,7 @@ def init_server(self, config, fd):
host = config.get('rpchost', '127.0.0.1')
port = config.get('rpcport', 0)
try:
server = SimpleJSONRPCServer((host, port), logRequests=False, requestHandler=RequestHandler)
server = SimpleJSONRPCServer((host, port), logRequests=False)
except Exception as e:
self.print_error('Warning: cannot initialize RPC server on host', host, e)
self.server = None
Expand Down
5 changes: 4 additions & 1 deletion lib/mnemonic.py
Original file line number Diff line number Diff line change
Expand Up @@ -171,7 +171,10 @@ def make_seed(self, seed_type='standard', num_bits=132, custom_entropy=1):
n_custom = int(math.ceil(math.log(custom_entropy, 2)))
n = max(16, num_bits - n_custom)
print_error("make_seed", prefix, "adding %d bits"%n)
my_entropy = ecdsa.util.randrange(pow(2, n))
my_entropy = 1
while my_entropy < pow(2, n - bpw):
# try again if seed would not contain enough words
my_entropy = ecdsa.util.randrange(pow(2, n))
nonce = 0
while True:
nonce += 1
Expand Down
2 changes: 1 addition & 1 deletion lib/pem.py
Original file line number Diff line number Diff line change
Expand Up @@ -165,7 +165,7 @@ def _parsePKCS8(_bytes):


def _parseSSLeay(bytes):
return _parseASN1PrivateKey(ASN1_Node(str(bytes)))
return _parseASN1PrivateKey(ASN1_Node(bytes))


def bytesToNumber(s):
Expand Down
48 changes: 44 additions & 4 deletions lib/tests/test_wallet_vertical.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,10 @@
import lib.storage as storage
import lib.wallet as wallet

#from plugins.trustedcoin import trustedcoin

# TODO: 2fa

# TODO passphrase/seed_extension
class TestWalletKeystoreAddressIntegrity(unittest.TestCase):

gap_limit = 1 # make tests run faster
Expand All @@ -32,12 +34,17 @@ def _create_standard_wallet(self, ks):
w.synchronize()
return w

def _create_multisig_wallet(self, ks1, ks2):
def _create_multisig_wallet(self, ks1, ks2, ks3=None):
"""Creates a 2-of-2 or 2-of-3 multisig wallet."""
store = storage.WalletStorage('if_this_exists_mocking_failed_648151893')
multisig_type = "%dof%d" % (2, 2)
store.put('wallet_type', multisig_type)
store.put('x%d/' % 1, ks1.dump())
store.put('x%d/' % 2, ks2.dump())
if ks3 is None:
multisig_type = "%dof%d" % (2, 2)
else:
multisig_type = "%dof%d" % (2, 3)
store.put('x%d/' % 3, ks3.dump())
store.put('wallet_type', multisig_type)
store.put('gap_limit', self.gap_limit)
w = wallet.Multisig_Wallet(store)
w.synchronize()
Expand Down Expand Up @@ -99,6 +106,39 @@ def test_electrum_seed_old(self, mock_write):
self.assertEqual(w.get_receiving_addresses()[0], 'MNCPTc38CQXQtXzmyKRnHPEdSe9A6RWaht')
self.assertEqual(w.get_change_addresses()[0], 'MSKfNFBVnGTNao6UiCV2uVwGF5kvm2QBtq')

#@mock.patch.object(storage.WalletStorage, '_write')
#def test_electrum_seed_2fa(self, mock_write):
#seed_words = 'kiss live scene rude gate step hip quarter bunker oxygen motor glove'
#self.assertEqual(bitcoin.seed_type(seed_words), '2fa')

#xprv1, xpub1, xprv2, xpub2 = trustedcoin.TrustedCoinPlugin.xkeys_from_seed(seed_words, '')

#ks1 = keystore.from_xprv(xprv1)
#self.assertTrue(isinstance(ks1, keystore.BIP32_KeyStore))
#self.assertEqual(ks1.xprv, 'xprv9uraXy9F3HP7i8QDqwNTBiD8Jf4bPD4Epif8cS8qbUbgeidUesyZpKmzfcSeHutsGfFnjgih7kzwTB5UQVRNB5LoXaNc8pFusKYx3KVVvYR')
#self.assertEqual(ks1.xpub, 'xpub68qvwUg8sewQvcUgwxuTYr9rrgu5nfn6BwajQpYT9p8fXWxdCRHpN86UWruWJAD1ede8Sv8ERrTa22Gyc4SBfm7zFpcyoVWVBKCVwnw6s1J')
#self.assertEqual(ks1.xpub, xpub1)

#ks2 = keystore.from_xprv(xprv2)
#self.assertTrue(isinstance(ks2, keystore.BIP32_KeyStore))
#self.assertEqual(ks2.xprv, 'xprv9uraXy9F3HP7kKSiRAvLV7Nrjj7YzspDys7dvGLLu4tLZT49CEBxPWp88dHhVxvZ69SHrPQMUCWjj4Ka2z9kNvs1HAeEf3extGGeSWqEVqf')
#self.assertEqual(ks2.xpub, 'xpub68qvwUg8sewQxoXBXCTLrFKbHkx3QLY5M63EiejxTQRKSFPHjmWCwK8byvZMM2wZNYA3SmxXoma3M1zxhGESHZwtB7SwrxRgKXAG8dCD2eS')
#self.assertEqual(ks2.xpub, xpub2)

#long_user_id, short_id = trustedcoin.get_user_id(
# {'x1/': {'xpub': xpub1},
# 'x2/': {'xpub': xpub2}})
#xpub3 = trustedcoin.make_xpub(trustedcoin.signing_xpub, long_user_id)
#ks3 = keystore.from_xpub(xpub3)
#self._check_xpub_keystore_sanity(ks3)
#self.assertTrue(isinstance(ks3, keystore.BIP32_KeyStore))

#w = self._create_multisig_wallet(ks1, ks2, ks3)
#self.assertEqual(w.txin_type, 'p2sh')

#self.assertEqual(w.get_receiving_addresses()[0], '35L8XmCDoEBKeaWRjvmZvoZvhp8BXMMMPV')
#self.assertEqual(w.get_change_addresses()[0], '3PeZEcumRqHSPNN43hd4yskGEBdzXgY8Cy')

@mock.patch.object(storage.WalletStorage, '_write')
def test_bip39_seed_bip44_standard(self, mock_write):
seed_words = 'treat dwarf wealth gasp brass outside high rent blood crowd make initial'
Expand Down
2 changes: 1 addition & 1 deletion lib/version.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
ELECTRUM_VERSION = '3.0.3' # version of the client package
ELECTRUM_VERSION = '3.0.4' # version of the client package
PROTOCOL_VERSION = '1.1' # protocol version requested

# The hash of the mnemonic seed must begin with this
Expand Down
2 changes: 1 addition & 1 deletion lib/wallet.py
Original file line number Diff line number Diff line change
Expand Up @@ -984,7 +984,7 @@ def prepare_for_verifier(self):
# if we are on a pruning server, remove unverified transactions
with self.lock:
vr = list(self.verified_tx.keys()) + list(self.unverified_tx.keys())
for tx_hash in self.transactions.keys():
for tx_hash in list(self.transactions):
if tx_hash not in vr:
self.print_error("removing transaction", tx_hash)
self.transactions.pop(tx_hash)
Expand Down
3 changes: 2 additions & 1 deletion plugins/ledger/ledger.py
Original file line number Diff line number Diff line change
Expand Up @@ -221,10 +221,11 @@ def decrypt_message(self, pubkey, message, password):
def sign_message(self, sequence, message, password):
self.signing = True
message = message.encode('utf8')
message_hash = hashlib.sha256(message).hexdigest().upper()
# prompt for the PIN before displaying the dialog if necessary
client = self.get_client()
address_path = self.get_derivation()[2:] + "/%d/%d"%sequence
self.handler.show_message("Signing message ...")
self.handler.show_message("Signing message ...\r\nMessage hash: "+message_hash)
try:
info = self.get_client().signMessagePrepare(address_path, message)
pin = ""
Expand Down

0 comments on commit 0bff3e4

Please sign in to comment.