| OLD | NEW | 
|    1 # This Source Code Form is subject to the terms of the Mozilla Public |    1 # This Source Code Form is subject to the terms of the Mozilla Public | 
|    2 # License, v. 2.0. If a copy of the MPL was not distributed with this |    2 # License, v. 2.0. If a copy of the MPL was not distributed with this | 
|    3 # file, You can obtain one at http://mozilla.org/MPL/2.0/. |    3 # file, You can obtain one at http://mozilla.org/MPL/2.0/. | 
|    4  |    4  | 
|    5 import base64 |    5 import base64 | 
|    6 import ConfigParser |    6 import ConfigParser | 
|    7 import json |    7 import json | 
|    8 import os |    8 import os | 
|    9 import re |    9 import re | 
|   10 from urlparse import urlparse |   10 from urlparse import urlparse | 
|   11  |   11  | 
|   12 from packager import readMetadata, getDefaultFileName, getBuildVersion, getTempl
     ate, Files |   12 from packager import readMetadata, getDefaultFileName, getBuildVersion, getTempl
     ate, Files | 
|   13 from packagerChrome import convertJS, importGeckoLocales, getIgnoredFiles, getPa
     ckageFiles, defaultLocale, createScriptPage |   13 from packagerChrome import convertJS, importGeckoLocales, getIgnoredFiles, getPa
     ckageFiles, defaultLocale, createScriptPage | 
|   14  |   14  | 
|   15 PRIVATE_KEY_REGEXP = r'-+BEGIN PRIVATE KEY-+(.*?)-+END PRIVATE KEY-+' |  | 
|   16 CERTIFICATE_REGEXP = r'-+BEGIN CERTIFICATE-+(.*?)-+END CERTIFICATE-+' |  | 
|   17  |  | 
|   18  |   15  | 
|   19 def processFile(path, data, params): |   16 def processFile(path, data, params): | 
|   20     return data |   17     return data | 
|   21  |   18  | 
|   22  |   19  | 
|   23 def createManifest(params, files): |   20 def createManifest(params, files): | 
|   24     template = getTemplate('Info.plist.tmpl', autoEscape=True) |   21     template = getTemplate('Info.plist.tmpl', autoEscape=True) | 
|   25     metadata = params['metadata'] |   22     metadata = params['metadata'] | 
|   26     catalog = json.loads(files['_locales/%s/messages.json' % defaultLocale]) |   23     catalog = json.loads(files['_locales/%s/messages.json' % defaultLocale]) | 
|   27  |   24  | 
| (...skipping 78 matching lines...) Expand 10 before | Expand all | Expand 10 after  Loading... | 
|  106 def fixAbsoluteUrls(files): |  103 def fixAbsoluteUrls(files): | 
|  107     for filename, content in files.iteritems(): |  104     for filename, content in files.iteritems(): | 
|  108         if os.path.splitext(filename)[1].lower() == '.html': |  105         if os.path.splitext(filename)[1].lower() == '.html': | 
|  109             files[filename] = re.sub( |  106             files[filename] = re.sub( | 
|  110                 r'(<[^<>]*?\b(?:href|src)\s*=\s*["\']?)\/+', |  107                 r'(<[^<>]*?\b(?:href|src)\s*=\s*["\']?)\/+', | 
|  111                 r'\1' + '/'.join(['..'] * filename.count('/') + ['']), |  108                 r'\1' + '/'.join(['..'] * filename.count('/') + ['']), | 
|  112                 content, re.S | re.I |  109                 content, re.S | re.I | 
|  113             ) |  110             ) | 
|  114  |  111  | 
|  115  |  112  | 
|  116 def get_certificates_and_key(keyfile): |  | 
|  117     from Crypto.PublicKey import RSA |  | 
|  118  |  | 
|  119     certs = [] |  | 
|  120     with open(keyfile, 'r') as file: |  | 
|  121         data = file.read() |  | 
|  122     match = re.search(PRIVATE_KEY_REGEXP, data, re.S) |  | 
|  123     if not match: |  | 
|  124         raise Exception('Cound not find private key in file') |  | 
|  125     key = RSA.importKey(match.group(0)) |  | 
|  126  |  | 
|  127     for match in re.finditer(CERTIFICATE_REGEXP, data, re.S): |  | 
|  128         certs.append(base64.b64decode(match.group(1))) |  | 
|  129  |  | 
|  130     return certs, key |  | 
|  131  |  | 
|  132  |  | 
|  133 def _get_sequence(data): |  113 def _get_sequence(data): | 
|  134     from Crypto.Util import asn1 |  114     from Crypto.Util import asn1 | 
|  135     sequence = asn1.DerSequence() |  115     sequence = asn1.DerSequence() | 
|  136     sequence.decode(data) |  116     sequence.decode(data) | 
|  137     return sequence |  117     return sequence | 
|  138  |  118  | 
|  139  |  119  | 
|  140 def get_developer_identifier(certs): |  120 def get_developer_identifier(certs): | 
|  141     for cert in certs: |  121     for cert in certs: | 
|  142         # See https://tools.ietf.org/html/rfc5280#section-4 |  122         # See https://tools.ietf.org/html/rfc5280#section-4 | 
|  143         tbscertificate = _get_sequence(cert)[0] |  123         tbscertificate = _get_sequence(base64.b64decode(cert))[0] | 
|  144         subject = _get_sequence(tbscertificate)[5] |  124         subject = _get_sequence(tbscertificate)[5] | 
|  145  |  125  | 
|  146         # We could decode the subject but since we have to apply a regular |  126         # We could decode the subject but since we have to apply a regular | 
|  147         # expression on CN entry anyway we can just skip that. |  127         # expression on CN entry anyway we can just skip that. | 
|  148         m = re.search(r'Safari Developer: \((\S*?)\)', subject) |  128         m = re.search(r'Safari Developer: \((\S*?)\)', subject) | 
|  149         if m: |  129         if m: | 
|  150             return m.group(1) |  130             return m.group(1) | 
|  151  |  131  | 
|  152     raise Exception('No Safari developer certificate found in chain') |  132     raise Exception('No Safari developer certificate found in chain') | 
|  153  |  133  | 
|  154  |  134  | 
|  155 def sign_digest(key, digest): |  | 
|  156     from Crypto.Hash import SHA |  | 
|  157     from Crypto.Signature import PKCS1_v1_5 |  | 
|  158  |  | 
|  159     # xar already calculated the SHA1 digest so we have to fake hashing here. |  | 
|  160     class FakeHash(SHA.SHA1Hash): |  | 
|  161         def digest(self): |  | 
|  162             return digest |  | 
|  163  |  | 
|  164     return PKCS1_v1_5.new(key).sign(FakeHash()) |  | 
|  165  |  | 
|  166  |  | 
|  167 def createSignedXarArchive(outFile, files, certs, key): |  | 
|  168     import subprocess |  | 
|  169     import tempfile |  | 
|  170     import shutil |  | 
|  171  |  | 
|  172     # write files to temporary directory and create a xar archive |  | 
|  173     dirname = tempfile.mkdtemp() |  | 
|  174     try: |  | 
|  175         for filename, contents in files.iteritems(): |  | 
|  176             path = os.path.join(dirname, filename) |  | 
|  177  |  | 
|  178             try: |  | 
|  179                 os.makedirs(os.path.dirname(path)) |  | 
|  180             except OSError: |  | 
|  181                 pass |  | 
|  182  |  | 
|  183             with open(path, 'wb') as file: |  | 
|  184                 file.write(contents) |  | 
|  185  |  | 
|  186         subprocess.check_output( |  | 
|  187             ['xar', '-czf', os.path.abspath(outFile), '--distribution'] + os.lis
     tdir(dirname), |  | 
|  188             cwd=dirname |  | 
|  189         ) |  | 
|  190     finally: |  | 
|  191         shutil.rmtree(dirname) |  | 
|  192  |  | 
|  193     certificate_filenames = [] |  | 
|  194     try: |  | 
|  195         # write each certificate in DER format to a separate |  | 
|  196         # temporary file, that they can be passed to xar |  | 
|  197         for cert in certs: |  | 
|  198             fd, filename = tempfile.mkstemp() |  | 
|  199             try: |  | 
|  200                 certificate_filenames.append(filename) |  | 
|  201                 os.write(fd, cert) |  | 
|  202             finally: |  | 
|  203                 os.close(fd) |  | 
|  204  |  | 
|  205         # add certificates and placeholder signature |  | 
|  206         # to the xar archive, and get data to sign |  | 
|  207         fd, digest_filename = tempfile.mkstemp() |  | 
|  208         os.close(fd) |  | 
|  209         try: |  | 
|  210             subprocess.check_call( |  | 
|  211                 [ |  | 
|  212                     'xar', '--sign', '-f', outFile, |  | 
|  213                     '--data-to-sign', digest_filename, |  | 
|  214                     '--sig-size', str(len(sign_digest(key, ''))) |  | 
|  215                 ] + [ |  | 
|  216                     arg for cert in certificate_filenames for arg in ('--cert-lo
     c', cert) |  | 
|  217                 ] |  | 
|  218             ) |  | 
|  219  |  | 
|  220             with open(digest_filename, 'rb') as file: |  | 
|  221                 digest = file.read() |  | 
|  222         finally: |  | 
|  223             os.unlink(digest_filename) |  | 
|  224     finally: |  | 
|  225         for filename in certificate_filenames: |  | 
|  226             os.unlink(filename) |  | 
|  227  |  | 
|  228     # sign data and inject signature into xar archive |  | 
|  229     fd, signature_filename = tempfile.mkstemp() |  | 
|  230     try: |  | 
|  231         try: |  | 
|  232             os.write(fd, sign_digest(key, digest)) |  | 
|  233         finally: |  | 
|  234             os.close(fd) |  | 
|  235  |  | 
|  236         subprocess.check_call(['xar', '--inject-sig', signature_filename, '-f', 
     outFile]) |  | 
|  237     finally: |  | 
|  238         os.unlink(signature_filename) |  | 
|  239  |  | 
|  240  |  | 
|  241 def createBuild(baseDir, type, outFile=None, buildNum=None, releaseBuild=False, 
     keyFile=None, devenv=False): |  135 def createBuild(baseDir, type, outFile=None, buildNum=None, releaseBuild=False, 
     keyFile=None, devenv=False): | 
|  242     metadata = readMetadata(baseDir, type) |  136     metadata = readMetadata(baseDir, type) | 
|  243     version = getBuildVersion(baseDir, metadata, releaseBuild, buildNum) |  137     version = getBuildVersion(baseDir, metadata, releaseBuild, buildNum) | 
|  244  |  138  | 
|  245     if not outFile: |  139     if not outFile: | 
|  246         outFile = getDefaultFileName(metadata, version, 'safariextz' if keyFile 
     else 'zip') |  140         outFile = getDefaultFileName(metadata, version, 'safariextz' if keyFile 
     else 'zip') | 
|  247  |  141  | 
|  248     params = { |  142     params = { | 
|  249         'type': type, |  143         'type': type, | 
|  250         'baseDir': baseDir, |  144         'baseDir': baseDir, | 
| (...skipping 19 matching lines...) Expand all  Loading... | 
|  270         ) |  164         ) | 
|  271  |  165  | 
|  272     if metadata.has_section('import_locales'): |  166     if metadata.has_section('import_locales'): | 
|  273         importGeckoLocales(params, files) |  167         importGeckoLocales(params, files) | 
|  274  |  168  | 
|  275     if metadata.has_option('general', 'testScripts'): |  169     if metadata.has_option('general', 'testScripts'): | 
|  276         files['qunit/index.html'] = createScriptPage(params, 'testIndex.html.tmp
     l', |  170         files['qunit/index.html'] = createScriptPage(params, 'testIndex.html.tmp
     l', | 
|  277                                                      ('general', 'testScripts')) |  171                                                      ('general', 'testScripts')) | 
|  278  |  172  | 
|  279     if keyFile: |  173     if keyFile: | 
|  280         certs, key = get_certificates_and_key(keyFile) |  174         from buildtools import xarfile | 
 |  175         certs = xarfile.read_certificates(keyFile) | 
|  281         params['developerIdentifier'] = get_developer_identifier(certs) |  176         params['developerIdentifier'] = get_developer_identifier(certs) | 
|  282  |  177  | 
|  283     files['lib/info.js'] = createInfoModule(params) |  178     files['lib/info.js'] = createInfoModule(params) | 
|  284     files['background.html'] = createScriptPage(params, 'background.html.tmpl', |  179     files['background.html'] = createScriptPage(params, 'background.html.tmpl', | 
|  285                                                 ('general', 'backgroundScripts')
     ) |  180                                                 ('general', 'backgroundScripts')
     ) | 
|  286     files['Info.plist'] = createManifest(params, files) |  181     files['Info.plist'] = createManifest(params, files) | 
|  287  |  182  | 
|  288     fixAbsoluteUrls(files) |  183     fixAbsoluteUrls(files) | 
|  289  |  184  | 
|  290     dirname = metadata.get('general', 'basename') + '.safariextension' |  185     dirname = metadata.get('general', 'basename') + '.safariextension' | 
|  291     for filename in files.keys(): |  186     for filename in files.keys(): | 
|  292         files[os.path.join(dirname, filename)] = files.pop(filename) |  187         files[os.path.join(dirname, filename)] = files.pop(filename) | 
|  293  |  188  | 
|  294     if not devenv and keyFile: |  189     if not devenv and keyFile: | 
|  295         createSignedXarArchive(outFile, files, certs, key) |  190         from buildtools import xarfile | 
 |  191         xarfile.create(outFile, files, keyFile) | 
|  296     else: |  192     else: | 
|  297         files.zip(outFile) |  193         files.zip(outFile) | 
| OLD | NEW |