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 |