Rietveld Code Review Tool
Help | Bug tracker | Discussion group | Source code

Side by Side Diff: packagerSafari.py

Issue 29349885: Issue 4340 - Drop dependency on external xar tool (Closed)
Patch Set: Created Aug. 16, 2016, 7:44 p.m.
Left:
Right:
Use n/p to move between diff chunks; N/P to move between comments.
Jump to:
View unified diff | Download patch
« no previous file with comments | « no previous file | xarfile.py » ('j') | no next file with comments »
Toggle Intra-line Diffs ('i') | Expand Comments ('e') | Collapse Comments ('c') | Show Comments Hide Comments ('s')
OLDNEW
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
6 import ConfigParser 5 import ConfigParser
7 import json 6 import json
8 import math
9 import os 7 import os
10 import re 8 import re
11 from urlparse import urlparse 9 from urlparse import urlparse
12 10
13 from packager import readMetadata, getDefaultFileName, getBuildVersion, getTempl ate, Files 11 from packager import readMetadata, getDefaultFileName, getBuildVersion, getTempl ate, Files
14 from packagerChrome import convertJS, importGeckoLocales, getIgnoredFiles, getPa ckageFiles, defaultLocale, createScriptPage 12 from packagerChrome import convertJS, importGeckoLocales, getIgnoredFiles, getPa ckageFiles, defaultLocale, createScriptPage
15 13
16 14
17 def processFile(path, data, params): 15 def processFile(path, data, params):
18 return data 16 return data
(...skipping 85 matching lines...) Expand 10 before | Expand all | Expand 10 after
104 def fixAbsoluteUrls(files): 102 def fixAbsoluteUrls(files):
105 for filename, content in files.iteritems(): 103 for filename, content in files.iteritems():
106 if os.path.splitext(filename)[1].lower() == '.html': 104 if os.path.splitext(filename)[1].lower() == '.html':
107 files[filename] = re.sub( 105 files[filename] = re.sub(
108 r'(<[^<>]*?\b(?:href|src)\s*=\s*["\']?)\/+', 106 r'(<[^<>]*?\b(?:href|src)\s*=\s*["\']?)\/+',
109 r'\1' + '/'.join(['..'] * filename.count('/') + ['']), 107 r'\1' + '/'.join(['..'] * filename.count('/') + ['']),
110 content, re.S | re.I 108 content, re.S | re.I
111 ) 109 )
112 110
113 111
114 def get_certificates_and_key(keyfile):
115 from Crypto.PublicKey import RSA
116
117 certs = []
118 with open(keyfile, 'r') as file:
119 data = file.read()
120 keydata = re.sub(r'(-+END PRIVATE KEY-+).*', r'\1', data, flags=re.S)
121 key = RSA.importKey(keydata)
122
123 for match in re.finditer(r'-+BEGIN CERTIFICATE-+(.*?)-+END CERTIFICATE-+', d ata, re.S):
124 certs.append(base64.b64decode(match.group(1)))
125
126 return certs, key
127
128
129 def get_developer_identifier(certs): 112 def get_developer_identifier(certs):
130 from Crypto.Util import asn1 113 from Crypto.Util import asn1
131 def get_sequence(data): 114 def get_sequence(data):
132 sequence = asn1.DerSequence() 115 sequence = asn1.DerSequence()
133 sequence.decode(data) 116 sequence.decode(data)
134 return sequence 117 return sequence
135 118
136 for cert in certs: 119 for cert in certs:
137 # See https://tools.ietf.org/html/rfc5280#section-4 120 # See https://tools.ietf.org/html/rfc5280#section-4
138 tbsCertificate = get_sequence(cert)[0] 121 tbsCertificate = get_sequence(cert)[0]
139 subject = get_sequence(tbsCertificate)[5] 122 subject = get_sequence(tbsCertificate)[5]
140 123
141 # We could decode the subject but since we have to apply a regular 124 # We could decode the subject but since we have to apply a regular
142 # expression on CN entry anyway we can just skip that. 125 # expression on CN entry anyway we can just skip that.
143 m = re.search(r'Safari Developer: \((\S*?)\)', subject) 126 m = re.search(r'Safari Developer: \((\S*?)\)', subject)
144 if m: 127 if m:
145 return m.group(1) 128 return m.group(1)
146 129
147 raise Exception('No Safari developer certificate found in chain') 130 raise Exception('No Safari developer certificate found in chain')
148 131
149 132
150 def sign_digest(key, digest):
151 from Crypto.Hash import SHA
152 from Crypto.Signature import PKCS1_v1_5
153
154 # xar already calculated the SHA1 digest so we have to fake hashing here.
155 class FakeHash(SHA.SHA1Hash):
156 def digest(self):
157 return digest
158
159 return PKCS1_v1_5.new(key).sign(FakeHash())
160
161
162 def createSignedXarArchive(outFile, files, certs, key):
163 import subprocess
164 import tempfile
165 import shutil
166
167 # write files to temporary directory and create a xar archive
168 dirname = tempfile.mkdtemp()
169 try:
170 for filename, contents in files.iteritems():
171 path = os.path.join(dirname, filename)
172
173 try:
174 os.makedirs(os.path.dirname(path))
175 except OSError:
176 pass
177
178 with open(path, 'wb') as file:
179 file.write(contents)
180
181 subprocess.check_output(
182 ['xar', '-czf', os.path.abspath(outFile), '--distribution'] + os.lis tdir(dirname),
183 cwd=dirname
184 )
185 finally:
186 shutil.rmtree(dirname)
187
188 certificate_filenames = []
189 try:
190 # write each certificate in DER format to a separate
191 # temporary file, that they can be passed to xar
192 for cert in certs:
193 fd, filename = tempfile.mkstemp()
194 try:
195 certificate_filenames.append(filename)
196 os.write(fd, cert)
197 finally:
198 os.close(fd)
199
200 # add certificates and placeholder signature
201 # to the xar archive, and get data to sign
202 fd, digest_filename = tempfile.mkstemp()
203 os.close(fd)
204 try:
205 subprocess.check_call(
206 [
207 'xar', '--sign', '-f', outFile,
208 '--data-to-sign', digest_filename,
209 '--sig-size', str(len(sign_digest(key, '')))
210 ] + [
211 arg for cert in certificate_filenames for arg in ('--cert-lo c', cert)
212 ]
213 )
214
215 with open(digest_filename, 'rb') as file:
216 digest = file.read()
217 finally:
218 os.unlink(digest_filename)
219 finally:
220 for filename in certificate_filenames:
221 os.unlink(filename)
222
223 # sign data and inject signature into xar archive
224 fd, signature_filename = tempfile.mkstemp()
225 try:
226 try:
227 os.write(fd, sign_digest(key, digest))
228 finally:
229 os.close(fd)
230
231 subprocess.check_call(['xar', '--inject-sig', signature_filename, '-f', outFile])
232 finally:
233 os.unlink(signature_filename)
234
235
236 def createBuild(baseDir, type, outFile=None, buildNum=None, releaseBuild=False, keyFile=None, devenv=False): 133 def createBuild(baseDir, type, outFile=None, buildNum=None, releaseBuild=False, keyFile=None, devenv=False):
237 metadata = readMetadata(baseDir, type) 134 metadata = readMetadata(baseDir, type)
238 version = getBuildVersion(baseDir, metadata, releaseBuild, buildNum) 135 version = getBuildVersion(baseDir, metadata, releaseBuild, buildNum)
239 136
240 if not outFile: 137 if not outFile:
241 outFile = getDefaultFileName(metadata, version, 'safariextz' if keyFile else 'zip') 138 outFile = getDefaultFileName(metadata, version, 'safariextz' if keyFile else 'zip')
242 139
243 params = { 140 params = {
244 'type': type, 141 'type': type,
245 'baseDir': baseDir, 142 'baseDir': baseDir,
(...skipping 19 matching lines...) Expand all
265 ) 162 )
266 163
267 if metadata.has_section('import_locales'): 164 if metadata.has_section('import_locales'):
268 importGeckoLocales(params, files) 165 importGeckoLocales(params, files)
269 166
270 if metadata.has_option('general', 'testScripts'): 167 if metadata.has_option('general', 'testScripts'):
271 files['qunit/index.html'] = createScriptPage(params, 'testIndex.html.tmp l', 168 files['qunit/index.html'] = createScriptPage(params, 'testIndex.html.tmp l',
272 ('general', 'testScripts')) 169 ('general', 'testScripts'))
273 170
274 if keyFile: 171 if keyFile:
275 certs, key = get_certificates_and_key(keyFile) 172 from buildtools import xarfile
173 certs = xarfile.read_certificates(keyFile)
276 params['developerIdentifier'] = get_developer_identifier(certs) 174 params['developerIdentifier'] = get_developer_identifier(certs)
277 175
278 files['lib/info.js'] = createInfoModule(params) 176 files['lib/info.js'] = createInfoModule(params)
279 files['background.html'] = createScriptPage(params, 'background.html.tmpl', 177 files['background.html'] = createScriptPage(params, 'background.html.tmpl',
280 ('general', 'backgroundScripts') ) 178 ('general', 'backgroundScripts') )
281 files['Info.plist'] = createManifest(params, files) 179 files['Info.plist'] = createManifest(params, files)
282 180
283 fixAbsoluteUrls(files) 181 fixAbsoluteUrls(files)
284 182
285 dirname = metadata.get('general', 'basename') + '.safariextension' 183 dirname = metadata.get('general', 'basename') + '.safariextension'
286 for filename in files.keys(): 184 for filename in files.keys():
287 files[os.path.join(dirname, filename)] = files.pop(filename) 185 files[os.path.join(dirname, filename)] = files.pop(filename)
288 186
289 if not devenv and keyFile: 187 if not devenv and keyFile:
290 createSignedXarArchive(outFile, files, certs, key) 188 from buildtools import xarfile
189 xarfile.create(outFile, files, keyFile)
291 else: 190 else:
292 files.zip(outFile) 191 files.zip(outFile)
OLDNEW
« no previous file with comments | « no previous file | xarfile.py » ('j') | no next file with comments »

Powered by Google App Engine
This is Rietveld