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 | |
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 Loading... |
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 Loading... |
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) |
OLD | NEW |