From cb6ff87fbb05e421f77b57a79699c647866ceb09 Mon Sep 17 00:00:00 2001 From: Filippo Valsorda 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 f07fc5d60d..564bde1d19 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 409f980bc2..dbb4c99e15 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 d5ca2d4bac..9766d905e3 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 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 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.