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

Side by Side Diff: packagerSafari.py

Issue 29349869: Issue 4339 - Replace M2Crypto by PyCrypto (Closed)
Patch Set: Merged cert and private key extraction Created Aug. 17, 2016, 2:09 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 | « packagerChrome.py ('k') | no next file » | 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
7 import json
5 import os 8 import os
6 import re 9 import re
7 import json
8 import ConfigParser
9 from urlparse import urlparse 10 from urlparse import urlparse
10 11
11 from packager import readMetadata, getDefaultFileName, getBuildVersion, getTempl ate, Files 12 from packager import readMetadata, getDefaultFileName, getBuildVersion, getTempl ate, Files
12 from packagerChrome import convertJS, importGeckoLocales, getIgnoredFiles, getPa ckageFiles, defaultLocale, createScriptPage 13 from packagerChrome import convertJS, importGeckoLocales, getIgnoredFiles, getPa ckageFiles, defaultLocale, createScriptPage
13 14
14 15
15 def processFile(path, data, params): 16 def processFile(path, data, params):
16 return data 17 return data
17 18
18 19
(...skipping 84 matching lines...) Expand 10 before | Expand all | Expand 10 after
103 for filename, content in files.iteritems(): 104 for filename, content in files.iteritems():
104 if os.path.splitext(filename)[1].lower() == '.html': 105 if os.path.splitext(filename)[1].lower() == '.html':
105 files[filename] = re.sub( 106 files[filename] = re.sub(
106 r'(<[^<>]*?\b(?:href|src)\s*=\s*["\']?)\/+', 107 r'(<[^<>]*?\b(?:href|src)\s*=\s*["\']?)\/+',
107 r'\1' + '/'.join(['..'] * filename.count('/') + ['']), 108 r'\1' + '/'.join(['..'] * filename.count('/') + ['']),
108 content, re.S | re.I 109 content, re.S | re.I
109 ) 110 )
110 111
111 112
112 def get_certificates_and_key(keyfile): 113 def get_certificates_and_key(keyfile):
113 import M2Crypto 114 from Crypto.PublicKey import RSA
114 115
115 certs = [] 116 with open(keyfile, 'r') as file:
116 bio = M2Crypto.BIO.openfile(keyfile) 117 data = file.read()
117 118
118 try: 119 certificates = []
119 key = M2Crypto.RSA.load_key_bio(bio) 120 key = None
120 bio.reset() 121 for match in re.finditer(r'-+BEGIN (.*?)-+(.*?)-+END \1-+', data, re.S):
121 while True: 122 section = match.group(1)
122 try: 123 if section == 'CERTIFICATE':
123 certs.append(M2Crypto.X509.load_cert_bio(bio)) 124 certificates.append(base64.b64decode(match.group(2)))
124 except M2Crypto.X509.X509Error: 125 elif section == 'PRIVATE KEY':
125 break 126 key = RSA.importKey(match.group(0))
126 finally: 127 if key is None:
Sebastian Noack 2016/08/17 18:34:11 Nit: Use |not x| instead |x is None| if it doesn't
Wladimir Palant 2016/08/17 19:21:12 Done.
127 bio.close() 128 raise Exception('Cound not find private key in file')
Sebastian Noack 2016/08/17 18:34:11 Typo: Cound -> Couldn't
Wladimir Palant 2016/08/17 19:21:12 Done.
128 129
129 return certs, key 130 return certificates, key
131
132
133 def _get_sequence(data):
134 from Crypto.Util import asn1
135 sequence = asn1.DerSequence()
136 sequence.decode(data)
137 return sequence
130 138
131 139
132 def get_developer_identifier(certs): 140 def get_developer_identifier(certs):
133 for cert in certs: 141 for cert in certs:
134 subject = cert.get_subject() 142 # See https://tools.ietf.org/html/rfc5280#section-4
135 for entry in subject.get_entries_by_nid(subject.nid['CN']): 143 tbscertificate = _get_sequence(cert)[0]
136 m = re.match(r'Safari Developer: \((.*?)\)', entry.get_data().as_tex t()) 144 subject = _get_sequence(tbscertificate)[5]
137 if m: 145
138 return m.group(1) 146 # We could decode the subject but since we have to apply a regular
147 # expression on CN entry anyway we can just skip that.
148 m = re.search(r'Safari Developer: \((\S*?)\)', subject)
149 if m:
150 return m.group(1)
139 151
140 raise Exception('No Safari developer certificate found in chain') 152 raise Exception('No Safari developer certificate found in chain')
141 153
142 154
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
143 def createSignedXarArchive(outFile, files, certs, key): 167 def createSignedXarArchive(outFile, files, certs, key):
144 import subprocess 168 import subprocess
145 import tempfile 169 import tempfile
146 import shutil 170 import shutil
147 import M2Crypto
148 171
149 # write files to temporary directory and create a xar archive 172 # write files to temporary directory and create a xar archive
150 dirname = tempfile.mkdtemp() 173 dirname = tempfile.mkdtemp()
151 try: 174 try:
152 for filename, contents in files.iteritems(): 175 for filename, contents in files.iteritems():
153 path = os.path.join(dirname, filename) 176 path = os.path.join(dirname, filename)
154 177
155 try: 178 try:
156 os.makedirs(os.path.dirname(path)) 179 os.makedirs(os.path.dirname(path))
157 except OSError: 180 except OSError:
(...skipping 10 matching lines...) Expand all
168 shutil.rmtree(dirname) 191 shutil.rmtree(dirname)
169 192
170 certificate_filenames = [] 193 certificate_filenames = []
171 try: 194 try:
172 # write each certificate in DER format to a separate 195 # write each certificate in DER format to a separate
173 # temporary file, that they can be passed to xar 196 # temporary file, that they can be passed to xar
174 for cert in certs: 197 for cert in certs:
175 fd, filename = tempfile.mkstemp() 198 fd, filename = tempfile.mkstemp()
176 try: 199 try:
177 certificate_filenames.append(filename) 200 certificate_filenames.append(filename)
178 os.write(fd, cert.as_der()) 201 os.write(fd, cert)
179 finally: 202 finally:
180 os.close(fd) 203 os.close(fd)
181 204
182 # add certificates and placeholder signature 205 # add certificates and placeholder signature
183 # to the xar archive, and get data to sign 206 # to the xar archive, and get data to sign
184 fd, digestinfo_filename = tempfile.mkstemp() 207 fd, digest_filename = tempfile.mkstemp()
185 os.close(fd) 208 os.close(fd)
186 try: 209 try:
187 subprocess.check_call( 210 subprocess.check_call(
188 [ 211 [
189 'xar', '--sign', '-f', outFile, 212 'xar', '--sign', '-f', outFile,
190 '--digestinfo-to-sign', digestinfo_filename, 213 '--data-to-sign', digest_filename,
191 '--sig-size', str(len(key.private_encrypt('', M2Crypto.RSA.p kcs1_padding))) 214 '--sig-size', str(len(sign_digest(key, '')))
192 ] + [ 215 ] + [
193 arg for cert in certificate_filenames for arg in ('--cert-lo c', cert) 216 arg for cert in certificate_filenames for arg in ('--cert-lo c', cert)
194 ] 217 ]
195 ) 218 )
196 219
197 with open(digestinfo_filename, 'rb') as file: 220 with open(digest_filename, 'rb') as file:
198 digestinfo = file.read() 221 digest = file.read()
199 finally: 222 finally:
200 os.unlink(digestinfo_filename) 223 os.unlink(digest_filename)
201 finally: 224 finally:
202 for filename in certificate_filenames: 225 for filename in certificate_filenames:
203 os.unlink(filename) 226 os.unlink(filename)
204 227
205 # sign data and inject signature into xar archive 228 # sign data and inject signature into xar archive
206 fd, signature_filename = tempfile.mkstemp() 229 fd, signature_filename = tempfile.mkstemp()
207 try: 230 try:
208 try: 231 try:
209 os.write(fd, key.private_encrypt( 232 os.write(fd, sign_digest(key, digest))
210 digestinfo,
211 M2Crypto.RSA.pkcs1_padding
212 ))
213 finally: 233 finally:
214 os.close(fd) 234 os.close(fd)
215 235
216 subprocess.check_call(['xar', '--inject-sig', signature_filename, '-f', outFile]) 236 subprocess.check_call(['xar', '--inject-sig', signature_filename, '-f', outFile])
217 finally: 237 finally:
218 os.unlink(signature_filename) 238 os.unlink(signature_filename)
219 239
220 240
221 def createBuild(baseDir, type, outFile=None, buildNum=None, releaseBuild=False, keyFile=None, devenv=False): 241 def createBuild(baseDir, type, outFile=None, buildNum=None, releaseBuild=False, keyFile=None, devenv=False):
222 metadata = readMetadata(baseDir, type) 242 metadata = readMetadata(baseDir, type)
(...skipping 45 matching lines...) Expand 10 before | Expand all | Expand 10 after
268 fixAbsoluteUrls(files) 288 fixAbsoluteUrls(files)
269 289
270 dirname = metadata.get('general', 'basename') + '.safariextension' 290 dirname = metadata.get('general', 'basename') + '.safariextension'
271 for filename in files.keys(): 291 for filename in files.keys():
272 files[os.path.join(dirname, filename)] = files.pop(filename) 292 files[os.path.join(dirname, filename)] = files.pop(filename)
273 293
274 if not devenv and keyFile: 294 if not devenv and keyFile:
275 createSignedXarArchive(outFile, files, certs, key) 295 createSignedXarArchive(outFile, files, certs, key)
276 else: 296 else:
277 files.zip(outFile) 297 files.zip(outFile)
OLDNEW
« no previous file with comments | « packagerChrome.py ('k') | no next file » | no next file with comments »

Powered by Google App Engine
This is Rietveld