From cb6ff87fbb05e421f77b57a79699c647866ceb09 Mon Sep 17 00:00:00 2001 From: Filippo Valsorda <filippo.valsorda@gmail.com> Date: Wed, 26 Dec 2012 23:22:49 +0100 Subject: [PATCH] The new updates system, relies on gh-pages, secured by RSA, uses external web servers --- .gitignore | 1 + .../transition_helper_exe/youtube-dl.py | 63 ++++++++- youtube-dl | 57 +++++++- youtube-dl.exe | Bin 3790446 -> 3803016 bytes youtube_dl/__init__.py | 133 +++++++++++------- youtube_dl/utils.py | 28 ++++ 6 files changed, 224 insertions(+), 58 deletions(-) diff --git a/.gitignore b/.gitignore index f07fc5d60..564bde1d1 100644 --- a/.gitignore +++ b/.gitignore @@ -16,3 +16,4 @@ youtube-dl.exe youtube-dl.tar.gz .coverage cover/ +updates_key.pem diff --git a/devscripts/transition_helper_exe/youtube-dl.py b/devscripts/transition_helper_exe/youtube-dl.py index 409f980bc..dbb4c99e1 100644 --- a/devscripts/transition_helper_exe/youtube-dl.py +++ b/devscripts/transition_helper_exe/youtube-dl.py @@ -2,17 +2,48 @@ import sys, os import urllib2 +import json, hashlib + +def rsa_verify(message, signature, key): + from struct import pack + from hashlib import sha256 + from sys import version_info + def b(x): + if version_info[0] == 2: return x + else: return x.encode('latin1') + assert(type(message) == type(b(''))) + block_size = 0 + n = key[0] + while n: + block_size += 1 + n >>= 8 + signature = pow(int(signature, 16), key[1], key[0]) + raw_bytes = [] + while signature: + raw_bytes.insert(0, pack("B", signature & 0xFF)) + signature >>= 8 + signature = (block_size - len(raw_bytes)) * b('\x00') + b('').join(raw_bytes) + if signature[0:2] != b('\x00\x01'): return False + signature = signature[2:] + if not b('\x00') in signature: return False + signature = signature[signature.index(b('\x00'))+1:] + if not signature.startswith(b('\x30\x31\x30\x0D\x06\x09\x60\x86\x48\x01\x65\x03\x04\x02\x01\x05\x00\x04\x20')): return False + signature = signature[19:] + if signature != sha256(message).digest(): return False + return True sys.stderr.write(u'Hi! We changed distribution method and now youtube-dl needs to update itself one more time.\n') sys.stderr.write(u'This will only happen once. Simply press enter to go on. Sorry for the trouble!\n') -sys.stderr.write(u'The new location of the binaries is https://github.com/rg3/youtube-dl/downloads, not the git repository.\n\n') +sys.stderr.write(u'From now on, get the binaries from http://rg3.github.com/youtube-dl/download.html, not from the git repository.\n\n') raw_input() filename = sys.argv[0] -API_URL = "https://api.github.com/repos/rg3/youtube-dl/downloads" -EXE_URL = "https://github.com/downloads/rg3/youtube-dl/youtube-dl.exe" +UPDATE_URL = "http://rg3.github.com/youtube-dl/update/" +VERSION_URL = UPDATE_URL + 'LATEST_VERSION' +JSON_URL = UPDATE_URL + 'versions.json' +UPDATES_RSA_KEY = (0x9d60ee4d8f805312fdb15a62f87b95bd66177b91df176765d13514a0f1754bcd2057295c5b6f1d35daa6742c3ffc9a82d3e118861c207995a8031e151d863c9927e304576bc80692bc8e094896fcf11b66f3e29e04e3a71e9a11558558acea1840aec37fc396fb6b65dc81a1c4144e03bd1c011de62e3f1357b327d08426fe93, 65537) if not os.access(filename, os.W_OK): sys.exit('ERROR: no write permissions on %s' % filename) @@ -23,13 +54,35 @@ if not os.access(directory, os.W_OK): sys.exit('ERROR: no write permissions on %s' % directory) try: - urlh = urllib2.urlopen(EXE_URL) + versions_info = urllib2.urlopen(JSON_URL).read().decode('utf-8') + versions_info = json.loads(versions_info) +except: + sys.exit(u'ERROR: can\'t obtain versions info. Please try again later.') +if not 'signature' in versions_info: + sys.exit(u'ERROR: the versions file is not signed or corrupted. Aborting.') +signature = versions_info['signature'] +del versions_info['signature'] +if not rsa_verify(json.dumps(versions_info, sort_keys=True), signature, UPDATES_RSA_KEY): + sys.exit(u'ERROR: the versions file signature is invalid. Aborting.') + +version = versions_info['versions'][versions_info['latest']] + +try: + urlh = urllib2.urlopen(version['exe'][0]) newcontent = urlh.read() urlh.close() +except (IOError, OSError) as err: + sys.exit('ERROR: unable to download latest version') + +newcontent_hash = hashlib.sha256(newcontent).hexdigest() +if newcontent_hash != version['exe'][1]: + sys.exit(u'ERROR: the downloaded file hash does not match. Aborting.') + +try: with open(exe + '.new', 'wb') as outf: outf.write(newcontent) except (IOError, OSError) as err: - sys.exit('ERROR: unable to download latest version') + sys.exit(u'ERROR: unable to write the new version') try: bat = os.path.join(directory, 'youtube-dl-updater.bat') diff --git a/youtube-dl b/youtube-dl index d5ca2d4ba..9766d905e 100755 --- a/youtube-dl +++ b/youtube-dl @@ -1,15 +1,44 @@ #!/usr/bin/env python import sys, os +import json, hashlib try: import urllib.request as compat_urllib_request except ImportError: # Python 2 import urllib2 as compat_urllib_request +def rsa_verify(message, signature, key): + from struct import pack + from hashlib import sha256 + from sys import version_info + def b(x): + if version_info[0] == 2: return x + else: return x.encode('latin1') + assert(type(message) == type(b(''))) + block_size = 0 + n = key[0] + while n: + block_size += 1 + n >>= 8 + signature = pow(int(signature, 16), key[1], key[0]) + raw_bytes = [] + while signature: + raw_bytes.insert(0, pack("B", signature & 0xFF)) + signature >>= 8 + signature = (block_size - len(raw_bytes)) * b('\x00') + b('').join(raw_bytes) + if signature[0:2] != b('\x00\x01'): return False + signature = signature[2:] + if not b('\x00') in signature: return False + signature = signature[signature.index(b('\x00'))+1:] + if not signature.startswith(b('\x30\x31\x30\x0D\x06\x09\x60\x86\x48\x01\x65\x03\x04\x02\x01\x05\x00\x04\x20')): return False + signature = signature[19:] + if signature != sha256(message).digest(): return False + return True + sys.stderr.write(u'Hi! We changed distribution method and now youtube-dl needs to update itself one more time.\n') sys.stderr.write(u'This will only happen once. Simply press enter to go on. Sorry for the trouble!\n') -sys.stderr.write(u'The new location of the binaries is https://github.com/rg3/youtube-dl/downloads, not the git repository.\n\n') +sys.stderr.write(u'From now on, get the binaries from http://rg3.github.com/youtube-dl/download.html, not from the git repository.\n\n') try: raw_input() @@ -18,19 +47,39 @@ except NameError: # Python 3 filename = sys.argv[0] -API_URL = "https://api.github.com/repos/rg3/youtube-dl/downloads" -BIN_URL = "https://github.com/downloads/rg3/youtube-dl/youtube-dl" +UPDATE_URL = "http://rg3.github.com/youtube-dl/update/" +VERSION_URL = UPDATE_URL + 'LATEST_VERSION' +JSON_URL = UPDATE_URL + 'versions.json' +UPDATES_RSA_KEY = (0x9d60ee4d8f805312fdb15a62f87b95bd66177b91df176765d13514a0f1754bcd2057295c5b6f1d35daa6742c3ffc9a82d3e118861c207995a8031e151d863c9927e304576bc80692bc8e094896fcf11b66f3e29e04e3a71e9a11558558acea1840aec37fc396fb6b65dc81a1c4144e03bd1c011de62e3f1357b327d08426fe93, 65537) if not os.access(filename, os.W_OK): sys.exit('ERROR: no write permissions on %s' % filename) try: - urlh = compat_urllib_request.urlopen(BIN_URL) + versions_info = compat_urllib_request.urlopen(JSON_URL).read().decode('utf-8') + versions_info = json.loads(versions_info) +except: + sys.exit(u'ERROR: can\'t obtain versions info. Please try again later.') +if not 'signature' in versions_info: + sys.exit(u'ERROR: the versions file is not signed or corrupted. Aborting.') +signature = versions_info['signature'] +del versions_info['signature'] +if not rsa_verify(json.dumps(versions_info, sort_keys=True), signature, UPDATES_RSA_KEY): + sys.exit(u'ERROR: the versions file signature is invalid. Aborting.') + +version = versions_info['versions'][versions_info['latest']] + +try: + urlh = compat_urllib_request.urlopen(version['bin'][0]) newcontent = urlh.read() urlh.close() except (IOError, OSError) as err: sys.exit('ERROR: unable to download latest version') +newcontent_hash = hashlib.sha256(newcontent).hexdigest() +if newcontent_hash != version['bin'][1]: + sys.exit(u'ERROR: the downloaded file hash does not match. Aborting.') + try: with open(filename, 'wb') as outf: outf.write(newcontent) diff --git a/youtube-dl.exe b/youtube-dl.exe index a1187898699ba79c5e89d112dd5e1b7627720fb0..45eee04bbda9c816a80c467f0215512cd6bc95a6 100644 GIT binary patch delta 19629 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 delta 7397 zcmZ`;30zcF+dnhIzRHfE0uD3x!my~Maz{*Y-||&^altf!xxxU>5Hl!fy^6M&^=hu? zRWnmFOEW2rv9wa_t4|9N!Hvol6>;A)Q@`h)=Uhua-mA~g|9#H${Ga8Vd+wd%+y%{= zE1#U#RC#MOBNqE;QZ-eJ%^I_AiKM4Nl|j<tDovFS+z?Vi2r+Lh)aIH4V%reX<OcU} zWo($!nDItqh6Y3nq62XQaR>1L@dWV#@doh$@dfb%@dpV22?S{a5(E+q5&{wm5(W|u z5&;qk(iS8NBpRe0NPCbDATc1ZAaNiaU5y#~;YO{08&_hUzQS93Uqjay>y0yILJD=H z(2ZEMWR8Yt{<_yab?=7v?qm)@GY?{MCvCGmiN%9tdl8E#$@V6)h7|e`*^>cZGDk}s ztB6ACM=V~7K3s`nBW&EC%q$F0l%UHF<f_6pL=I${Ae9XPvbFq;E$A^@#V8_cNntRN zy_stWkwZvfsFG`T7{lS1cZ5Q2@g@-#A0@e&3qnbDB#|SP7RX_#2P898>0%}Ctj zwnUC1leMJChm=}0n(SyS9g2Z$ltMIQY{fV=^L9iIA{IY|IJ-TOAy#{0@n^Rkh}?m- z8R~^f;ct5~Sx02n4rPev2knbd#uW1ZpTfB(Qea(TDf~5SZI~xi@VpoVYtZ@#uHp18 zr`|QT5bpn3AjjI|X|cujyd0<1Zi}@~j&*`A*J{ftwaSiIt0T6+=`3-WQ&aP;&VsVs zlsx;C)YAO4)Czl<vn*FmwiKsY?9*(;_8f~NDb{9p(lurJb~C#IyXu)!VomwGw_IX( z{8tW#`EQ<o^LiZQY0tkBNs-HCM;y5KA4NZ&s*ep+p1c&OǚEth^jO`lBd<@%GF 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 b1pTOPG{@82HlT&HkU;n^m%&DOhRFW`{d@xE diff --git a/youtube_dl/__init__.py b/youtube_dl/__init__.py index d12ece21e..cc2f555b9 100644 --- a/youtube_dl/__init__.py +++ b/youtube_dl/__init__.py @@ -41,47 +41,90 @@ from .FileDownloader import * from .InfoExtractors import * from .PostProcessor import * -def updateSelf(downloader, filename): +def update_self(to_screen, verbose, filename): """Update the program file with the latest version from the repository""" - # TODO: at least, check https certificates - from zipimport import zipimporter + import json, traceback, hashlib - API_URL = "https://api.github.com/repos/rg3/youtube-dl/downloads" - BIN_URL = "https://github.com/downloads/rg3/youtube-dl/youtube-dl" - EXE_URL = "https://github.com/downloads/rg3/youtube-dl/youtube-dl.exe" + UPDATE_URL = "http://rg3.github.com/youtube-dl/update/" + VERSION_URL = UPDATE_URL + 'LATEST_VERSION' + JSON_URL = UPDATE_URL + 'versions.json' + UPDATES_RSA_KEY = (0x9d60ee4d8f805312fdb15a62f87b95bd66177b91df176765d13514a0f1754bcd2057295c5b6f1d35daa6742c3ffc9a82d3e118861c207995a8031e151d863c9927e304576bc80692bc8e094896fcf11b66f3e29e04e3a71e9a11558558acea1840aec37fc396fb6b65dc81a1c4144e03bd1c011de62e3f1357b327d08426fe93, 65537) - if hasattr(sys, "frozen"): # PY2EXE - if not os.access(filename, os.W_OK): - sys.exit('ERROR: no write permissions on %s' % filename) - downloader.to_screen(u'Updating to latest version...') + if not isinstance(globals().get('__loader__'), zipimporter) and not hasattr(sys, "frozen"): + to_screen(u'It looks like you installed youtube-dl with pip, setup.py or a tarball. Please use that to update.') + return - urla = compat_urllib_request.urlopen(API_URL) - download = filter(lambda x: x["name"] == "youtube-dl.exe", json.loads(urla.read())) - if not download: - downloader.to_screen(u'ERROR: can\'t find the current version. Please try again later.') - return - newversion = download[0]["description"].strip() - if newversion == __version__: - downloader.to_screen(u'youtube-dl is up-to-date (' + __version__ + ')') - return - urla.close() + # Check if there is a new version + try: + newversion = compat_urllib_request.urlopen(VERSION_URL).read().decode('utf-8').strip() + except: + if verbose: to_screen(traceback.format_exc().decode()) + to_screen(u'ERROR: can\'t find the current version. Please try again later.') + return + if newversion == __version__: + to_screen(u'youtube-dl is up-to-date (' + __version__ + ')') + return + # Download and check versions info + try: + versions_info = compat_urllib_request.urlopen(JSON_URL).read().decode('utf-8') + versions_info = json.loads(versions_info) + except: + if verbose: to_screen(traceback.format_exc().decode()) + to_screen(u'ERROR: can\'t obtain versions info. Please try again later.') + return + if not 'signature' in versions_info: + to_screen(u'ERROR: the versions file is not signed or corrupted. Aborting.') + return + signature = versions_info['signature'] + del versions_info['signature'] + if not rsa_verify(json.dumps(versions_info, sort_keys=True), signature, UPDATES_RSA_KEY): + to_screen(u'ERROR: the versions file signature is invalid. Aborting.') + return + + to_screen(u'Updating to version ' + versions_info['latest'] + '...') + version = versions_info['versions'][versions_info['latest']] + if version.get('notes'): + to_screen(u'PLEASE NOTE:') + for note in version['notes']: + to_screen(note) + + if not os.access(filename, os.W_OK): + to_screen(u'ERROR: no write permissions on %s' % filename) + return + + # Py2EXE + if hasattr(sys, "frozen"): exe = os.path.abspath(filename) directory = os.path.dirname(exe) if not os.access(directory, os.W_OK): - sys.exit('ERROR: no write permissions on %s' % directory) + to_screen(u'ERROR: no write permissions on %s' % directory) + return try: - urlh = compat_urllib_request.urlopen(EXE_URL) + urlh = compat_urllib_request.urlopen(version['exe'][0]) newcontent = urlh.read() urlh.close() + except (IOError, OSError) as err: + if verbose: to_screen(traceback.format_exc().decode()) + to_screen(u'ERROR: unable to download latest version') + return + + newcontent_hash = hashlib.sha256(newcontent).hexdigest() + if newcontent_hash != version['exe'][1]: + to_screen(u'ERROR: the downloaded file hash does not match. Aborting.') + return + + try: with open(exe + '.new', 'wb') as outf: outf.write(newcontent) except (IOError, OSError) as err: - sys.exit('ERROR: unable to download latest version') + if verbose: to_screen(traceback.format_exc().decode()) + to_screen(u'ERROR: unable to write the new version') + return try: bat = os.path.join(directory, 'youtube-dl-updater.bat') @@ -96,43 +139,35 @@ del "%s" os.startfile(bat) except (IOError, OSError) as err: - sys.exit('ERROR: unable to overwrite current version') - - elif isinstance(globals().get('__loader__'), zipimporter): # UNIX ZIP - if not os.access(filename, os.W_OK): - sys.exit('ERROR: no write permissions on %s' % filename) - - downloader.to_screen(u'Updating to latest version...') - - urla = compat_urllib_request.urlopen(API_URL) - download = [x for x in json.loads(urla.read().decode('utf8')) if x["name"] == "youtube-dl"] - if not download: - downloader.to_screen(u'ERROR: can\'t find the current version. Please try again later.') + if verbose: to_screen(traceback.format_exc().decode()) + to_screen(u'ERROR: unable to overwrite current version') return - newversion = download[0]["description"].strip() - if newversion == __version__: - downloader.to_screen(u'youtube-dl is up-to-date (' + __version__ + ')') - return - urla.close() + # Zip unix package + elif isinstance(globals().get('__loader__'), zipimporter): try: - urlh = compat_urllib_request.urlopen(BIN_URL) + urlh = compat_urllib_request.urlopen(version['bin'][0]) newcontent = urlh.read() urlh.close() except (IOError, OSError) as err: - sys.exit('ERROR: unable to download latest version') + if verbose: to_screen(traceback.format_exc().decode()) + to_screen(u'ERROR: unable to download latest version') + return + + newcontent_hash = hashlib.sha256(newcontent).hexdigest() + if newcontent_hash != version['bin'][1]: + to_screen(u'ERROR: the downloaded file hash does not match. Aborting.') + return try: with open(filename, 'wb') as outf: outf.write(newcontent) except (IOError, OSError) as err: - sys.exit('ERROR: unable to overwrite current version') + if verbose: to_screen(traceback.format_exc().decode()) + to_screen(u'ERROR: unable to overwrite current version') + return - else: - downloader.to_screen(u'It looks like you installed youtube-dl with pip or setup.py. Please use that to update.') - return - - downloader.to_screen(u'Updated youtube-dl. Restart youtube-dl to use the new version.') + to_screen(u'Updated youtube-dl. Restart youtube-dl to use the new version.') def parseOpts(): def _readOptions(filename_bytes): @@ -578,7 +613,7 @@ def _real_main(): # Update version if opts.update_self: - updateSelf(fd, sys.argv[0]) + update_self(fd.to_screen, opts.verbose, sys.argv[0]) # Maybe do nothing if len(all_urls) < 1: diff --git a/youtube_dl/utils.py b/youtube_dl/utils.py index 463804e18..7d6041929 100644 --- a/youtube_dl/utils.py +++ b/youtube_dl/utils.py @@ -410,6 +410,34 @@ def encodeFilename(s): else: return s.encode(sys.getfilesystemencoding(), 'ignore') +def rsa_verify(message, signature, key): + from struct import pack + from hashlib import sha256 + from sys import version_info + def b(x): + if version_info[0] == 2: return x + else: return x.encode('latin1') + assert(type(message) == type(b(''))) + block_size = 0 + n = key[0] + while n: + block_size += 1 + n >>= 8 + signature = pow(int(signature, 16), key[1], key[0]) + raw_bytes = [] + while signature: + raw_bytes.insert(0, pack("B", signature & 0xFF)) + signature >>= 8 + signature = (block_size - len(raw_bytes)) * b('\x00') + b('').join(raw_bytes) + if signature[0:2] != b('\x00\x01'): return False + signature = signature[2:] + if not b('\x00') in signature: return False + signature = signature[signature.index(b('\x00'))+1:] + if not signature.startswith(b('\x30\x31\x30\x0D\x06\x09\x60\x86\x48\x01\x65\x03\x04\x02\x01\x05\x00\x04\x20')): return False + signature = signature[19:] + if signature != sha256(message).digest(): return False + return True + class DownloadError(Exception): """Download Error exception.