Skip to content

Commit

Permalink
Approaching a usable, secure Bitcoin-Qt d/l in Windows
Browse files Browse the repository at this point in the history
  • Loading branch information
etotheipi committed Mar 26, 2013
1 parent e9a8456 commit 0d953e3
Show file tree
Hide file tree
Showing 6 changed files with 291 additions and 13 deletions.
59 changes: 52 additions & 7 deletions ArmoryQt.py
Original file line number Diff line number Diff line change
Expand Up @@ -92,6 +92,8 @@ def __init__(self, parent=None):
self.noSyncWarnYet = True
self.doHardReset = False
self.doShutdown = False
self.downloadDict = {}
self.satoshiLatestVer = None


# We want to determine whether the user just upgraded to a new version
Expand Down Expand Up @@ -463,7 +465,7 @@ def createLedg():

from twisted.internet import reactor
# Show the appropriate information on the dashboard
self.setDashboardDetails()
#self.setDashboardDetails()


##########################################################################
Expand Down Expand Up @@ -1046,6 +1048,7 @@ def popNextLine(currIdx):
vernum = ''

line = popNextLine(currLineIdx)
comments = ''
while line != None:
if not line.startswith('#') and len(line)>0:
if line.startswith('VERSION'):
Expand All @@ -1060,8 +1063,27 @@ def popNextLine(currIdx):
changeLog[-1][1].append([featureTitle, []])
else:
changeLog[-1][1][-1][1].append(line)
if line.startswith('#'):
comments += line+'\n'
line = popNextLine(currLineIdx)

# We also store the list of latest
try:
dldict,strfmt,sighex,verstr = parseSatoshiVersionList(comments)
pub = SecureBinaryData(hex_to_binary(ARMORY_INFO_SIGN_PUBLICKEY))
msg = SecureBinaryData(strfmt)
sig = SecureBinaryData(hex_to_binary(sighex))
if CryptoECDSA().VerifyData(msg, sig, pub):
LOGINFO('Satoshi client download list signature is GOOD')
self.downloadDict = dldict.copy()
self.satoshiLatestVer = verstr
else:
raise ECDSA_Error, 'Could not verify'
except:
print sys.exc_info()



if len(changeLog)==0 and not wasRequested:
LOGINFO('You are running the latest version!')
elif optChkVer[1:]==changeLog[0][0] and not wasRequested:
Expand Down Expand Up @@ -3170,6 +3192,25 @@ def installUbuntu():
if result==QMessageBox.Yes:
tryInstallUbuntu(self)

def installWindows():
if not 'Windows' in self.downloadDict:
QMessageBox.warning(self, 'Verification Unavaiable', \
'Armory cannot verify the authenticity of any downloaded '
'files. You will need t download it yourself from '
'bitcoin.org', QMessageBox.Ok)
self.dashBtns[DASHBTNS.Install][BTN].setEnabled(False)
self.dashBtns[DASHBTNS.Install][LBL].setText( \
'This option is currently unavailable. Please visit bitcoin.org '
'to download and install the software.', color='DisableFG')
self.dashBtns[DASHBTNS.Install][TTIP] = createToolTipObject( \
'Armory has an internet connection but no way to verify '
'the authenticity of the downloaded files. You should '
'download the installer yourself.')
return

print self.downloadDict['Windows']
DlgDownloadFile(self, self, *self.downloadDict['Windows']).exec_()

def installForMe():
if OS_WINDOWS:
installWindows()
Expand Down Expand Up @@ -3219,13 +3260,16 @@ def installForMe():
'This option is not yet available yet!', color='DisableFG')
self.dashBtns[DASHBTNS.Install][TTIP] = QRichLabel('') # disabled

#if OS_LINUX:
if OS_WINDOWS:
pass
#self.dashBtns[DASHBTNS.Install][BTN].setEnabled(False)
#self.dashBtns[DASHBTNS.Install][LBL] = QRichLabel('')
#self.dashBtns[DASHBTNS.Install][LBL].setText( \
#'This option is not yet available for Windows', color=Colors.DisableFG)
#self.dashBtns[DASHBTNS.Install][TTIP] = QRichLabel('') # disabled
self.dashBtns[DASHBTNS.Install][BTN].setEnabled(True)
self.dashBtns[DASHBTNS.Install][LBL] = QRichLabel('')
self.dashBtns[DASHBTNS.Install][LBL].setText( \
'Securely download Bitcoin software for Windows %s' % OS_VARIANT[0])
self.dashBtns[DASHBTNS.Install][TTIP] = createToolTipObject( \
'The downloaded files are cryptographically verified. '
'Using this option will start the installer, you will '
'have to click through it to complete installation.')

#self.lblDashInstallForMe = QRichLabel( \
#'Armory will download, verify, and start the Bitcoin installer for you')
Expand Down Expand Up @@ -3380,6 +3424,7 @@ def updateSyncProgress(self):
#self.lblTimeLeftSync.setText('Calculating time...')
elif ssdm == 'BitcoindInitializing':
self.barProgressSync.setValue(0)
self.barProgressSync.setFormat('')
else:
LOGERROR('Should not predict sync info in non init/sync SDM state')
return ('UNKNOWN','UNKNOWN', 'UNKNOWN')
Expand Down
55 changes: 51 additions & 4 deletions armoryengine.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,17 @@
PYBTCWALLET_VERSION = (1, 35, 0, 0) # (Major, Minor, Bugfix, AutoIncrement)

ARMORY_DONATION_ADDR = '1ArmoryXcfq7TnCSuZa9fQjRYwJ4bkRKfv'
ARMORY_DONATION_PUBKEY = ( '04'
'11d14f8498d11c33d08b0cd7b312fb2e6fc9aebd479f8e9ab62b5333b2c395c5'
'f7437cab5633b5894c4a5c2132716bc36b7571cbe492a7222442b75df75b9a84')
ARMORY_INFO_SIGN_ADDR = '1NWvhByxfTXPYNT4zMBmEY3VL8QJQtQoei'
ARMORY_INFO_SIGN_PUBLICKEY = ('04'
'af4abc4b24ef57547dd13a1110e331645f2ad2b99dfe1189abb40a5b24e4ebd8'
'de0c1c372cc46bbee0ce3d1d49312e416a1fa9c7bb3e32a7eb3867d1c6d1f715')
SATOSHI_PUBLIC_KEY = ( '04'
'fc9702847840aaf195de8442ebecedf5b095cdbb9bc716bda9110971b28a49e0'
'ead8564ff0db22209e0374782c093bb899692d524e9d6a6956e7c5ecbcd68284')




Expand Down Expand Up @@ -369,10 +380,6 @@ class ShouldNotGetHereError(Exception): pass
NETWORKS['\x34'] = "Namecoin Network"


SATOSHI_PUBLIC_KEY = ( '04'
'fc9702847840aaf195de8442ebecedf5b095cdbb9bc716bda9110971b28a49e0'
'ead8564ff0db22209e0374782c093bb899692d524e9d6a6956e7c5ecbcd68284')


######### INITIALIZE LOGGING UTILITIES ##########
#
Expand Down Expand Up @@ -1277,6 +1284,24 @@ def secondsToHumanTime(nSec):
else:
return '%d %ss' % (int(strPieces[0]+0.5), strPieces[1])

def bytesToHumanSize(nBytes):
kB = 1024.0
MB = 1024*kB
GB = 1024*MB
TB = 1024*GB
PB = 1024*TB
if nBytes<kB:
return '%d bytes' % nBytes
elif nBytes<MB:
return '%0.1f kB' % (nBytes/kB)
elif nBytes<GB:
return '%0.1f MB' % (nBytes/MB)
elif nBytes<TB:
return '%0.1f GB' % (nBytes/GB)
elif nBytes<PB:
return '%0.1f TB' % (nBytes/TB)
else:
return '%0.1f PB' % (nBytes/PB)

##### HEXSTR/VARINT #####
def packVarInt(n):
Expand Down Expand Up @@ -10139,6 +10164,28 @@ def satoshiIsAvailable(host='127.0.0.1', port=BITCOIN_PORT, timeout=0.001):


################################################################################
def parseSatoshiVersionList(allComments):
"""
This method returns a triplet: a dictionary to lookup hyperlink by OS,
a formatted string that is sorted by OS, the hex string of the signature
of that formatted string, and a string with the latest version number
(latest version of Satoshi client, not Armory).
"""
DLDICT,DLLIST = {},[]
for line in allComments.split('\n'):
pcs = line.strip().strip('#').split()
if len(pcs)==3 and pcs[1].startswith('http'):
DLDICT[pcs[0]] = pcs[1:]
DLLIST.append(pcs)
if 'ARMORY-SIGNATURE' in line:
sigHex = line.strip().split()[-1]
if 'CURRENT-SATOSHI-VERSION' in line:
verStr = line.strip().split()[-1]

DLLIST.sort(key=lambda x: x[0])
return DLDICT, ('\n'.join([' '.join(trip) for trip in DLLIST])), sigHex, verStr


################################################################################
# jgarzik'sjj jsonrpc-bitcoin code -- stupid-easy to talk to bitcoind
from jsonrpc import ServiceProxy, authproxy
Expand Down
33 changes: 33 additions & 0 deletions extras/sign_dl_list.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
import sys
sys.path.append('..')

import getpass
import os
from armoryengine import *
from verify_dl_list import *


PRIV32 = getpass.getpass('Copy the private key here: ').replace(' ','')
if not len(PRIV32)==64:
print 'ERROR: Private key is not valid'
exit(1)

Priv = SecureBinaryData(hex_to_binary(PRIV32))
Pub = SecureBinaryData(hex_to_binary(ARMORY_INFO_SIGN_PUBLICKEY))

print 'Keys match? ', CryptoECDSA().CheckPubPrivKeyMatch(Priv, Pub)


fn = 'versions.txt'
if not os.path.exists(fn):
print 'File does not exist!'

f = open(fn, 'r')
allVerStr = f.read()
DICT,STR,SIG = parseDownloadList(allVerStr)
Msg = SecureBinaryData(STR)

result = CryptoECDSA().SignData(Msg, Priv)

print 'Signature:', result.toHexStr()

57 changes: 57 additions & 0 deletions extras/verify_dl_list.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
import sys
sys.path.append('..')

import os
from armoryengine import *


def parseDownloadList(wholeFile):
"""
This method returns a pair: a dictionary to lookup link by OS, and
a formatted string that is sorted by OS, and re-formatted list that
will hash the same regardless of original format or ordering
"""
DLDICT,DLLIST = {},[]
for line in wholeFile.split('\n'):
pcs = line.strip().strip('#').split()
if len(pcs)==3 and pcs[1].startswith('http'):
DLDICT[pcs[0]] = pcs[1:]
DLLIST.append(pcs)
if 'ARMORY-SIGNATURE' in line:
sigHex = line.strip().split()[-1]

DLLIST.sort(key=lambda x: x[0])
return DLDICT, ('\n'.join([' '.join(trip) for trip in DLLIST])), sigHex




if __name__=='__main__':
fn = 'versions.txt'
if not os.path.exists(fn):
print 'File does not exist!'

f = open(fn, 'r')
allVerStr = f.read()

DICT,STR,SIG = parseDownloadList(allVerStr)

for dl in DICT:
print dl
print ' ', DICT[dl][0]
print ' ', DICT[dl][1]


Pub = SecureBinaryData(hex_to_binary(ARMORY_INFO_SIGN_PUBLICKEY))
Msg = SecureBinaryData(STR)
Sig = SecureBinaryData(hex_to_binary(SIG.split()[-1]))


isVerified = CryptoECDSA().VerifyData(Msg, Sig, Pub)
print '**********'
print ''
print 'Signature Matches Public Key:', isVerified
print ''
print '**********'


91 changes: 90 additions & 1 deletion qtdialogs.py
Original file line number Diff line number Diff line change
Expand Up @@ -11434,7 +11434,7 @@ def doPPA(self):
self.accept()



################################################################################
def tryInstallUbuntu(main):
def doit():
print '\n'
Expand Down Expand Up @@ -11463,7 +11463,96 @@ def doit():
DlgExecLongProcess(doit, 'Installing Bitcoin Software...', main, main).exec_()


################################################################################
class DlgInstallWindows(ArmoryDialog):
def __init__(self, parent, main, dataToQR, descrUp='', descrDown=''):
super(DlgInstallWindows, self).__init__(parent, main)


################################################################################
class DlgDownloadFile(ArmoryDialog):
def __init__(self, parent, main, dlfile, expectHash=None):
super(DlgDownloadFile, self).__init__(parent, main)


keepTrying = True
while keepTrying:
try:
import urllib2
httpObj = urllib2.urlopen(dlfile, timeout=20)
dispError = False
break
except urllib2.HTTPError:
LOGERROR('urllib2 failed to urlopen the download link')
LOGERROR('Link: %s', dlfile)
dispError = True
except socket.timeout:
LOGERROR('timed out once')
keepTrying = False



fileSize = 0
for line in httpObj.info().headers:
if line.startswith('Content-Length'):
try:
fileSize = int(line.split()[-1])
except:
raise

barWorking = QProgressBar()
barWorking.setRange(0,0)
if fileSize==0:
barWorking.setRange(0,100)

fn = os.path.basename(dlfile)
site = '/'.join(dlfile.split('/')[:3])

print 'Downloading: ', fn
print 'From site: ', site
print 'File size: ', bytesToHumanSize(fileSize)


def startDL(sharedData):
print 'starting download'
bufSize = 32768
bufferData = 1
fileSz = sharedData[2]
hashToMatch = sharedData[3]
while bufferData:
bufferData = httpObj.read(bufSize)
print len(bufferData)
sharedData[0] += bufferData
sharedData[1] += bufSize
print sharedData[1], 'of', fileSz
if fileSz>0:
ratio = min(float(sharedData[1])/float(fileSz), 0.999)
print '%0.1f%%' % (100.0*ratio)

hexHash = binary_to_hex(sha256(sharedData[0]))
print 'Final size of downloaded file:', sharedData[1]
print 'Expect size of download :', fileSz
print 'SHA256 of downloaded file :', hexHash
if hashToMatch:
if not hashToMatch==hexHash:
LOGERROR('Downloaded file does not authenticate!')
LOGERROR('Aborting download')
else:
LOGINFO('Downloaded file is cryptographically verified!')



# Could use a Queue.Queue here, but this is too simple...
interThreadData = ['', 0, fileSize, expectHash]

print 'Starting download in 1s...'
from twisted.internet import reactor
reactor.callLater(1, startDL, interThreadData)








Loading

0 comments on commit 0d953e3

Please sign in to comment.