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: Added template for ToC generation and rebased Created Aug. 17, 2016, 10:04 a.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') | xarfile.py » ('J')
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 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
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
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)
OLDNEW
« no previous file with comments | « no previous file | xarfile.py » ('j') | xarfile.py » ('J')

Powered by Google App Engine
This is Rietveld