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

Delta Between Two Patch Sets: xarfile.py

Issue 29349885: Issue 4340 - Drop dependency on external xar tool (Closed)
Left Patch Set: Created Aug. 16, 2016, 7:44 p.m.
Right Patch Set: Addressed more nits Created Aug. 17, 2016, 7:21 p.m.
Left:
Right:
Use n/p to move between diff chunks; N/P to move between comments.
Jump to:
Left: Side by side diff | Download
Right: Side by side diff | Download
« no previous file with change/comment | « packagerSafari.py ('k') | xartoc.xml.tmpl » ('j') | no next file with change/comment »
Toggle Intra-line Diffs ('i') | Expand Comments ('e') | Collapse Comments ('c') | Show Comments Hide Comments ('s')
LEFTRIGHT
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 re 5 import re
7 import struct 6 import struct
8 import time 7 import time
9 from xml.etree import ElementTree
10 import zlib 8 import zlib
11 9
12 from Crypto.Hash import SHA 10 from Crypto.Hash import SHA
13 from Crypto.PublicKey import RSA 11 from Crypto.PublicKey import RSA
14 from Crypto.Signature import PKCS1_v1_5 12 from Crypto.Signature import PKCS1_v1_5
15 13
14 from buildtools.packager import getTemplate
15
16 XAR_HEADER = struct.Struct('>IHHQQI')
16 XAR_HEADER_MAGIC = 0x78617221 17 XAR_HEADER_MAGIC = 0x78617221
17 XAR_HEADER_SIZE = 28
18 XAR_VERSION = 1 18 XAR_VERSION = 1
19 XAR_CKSUM_SHA1 = 1 19 XAR_CKSUM_SHA1 = 1
20 20
21 def read_key(keyfile): 21
22 def read_certificates_and_key(keyfile):
22 with open(keyfile, 'r') as file: 23 with open(keyfile, 'r') as file:
23 data = file.read() 24 data = file.read()
24 data = re.sub(r'(-+END PRIVATE KEY-+).*', r'\1', data, flags=re.S)
25 return RSA.importKey(data)
26 25
27 def read_certificates(keyfile): 26 certificates = []
28 certs = [] 27 key = None
29 with open(keyfile, 'r') as file: 28 for match in re.finditer(r'-+BEGIN (.*?)-+(.*?)-+END \1-+', data, re.S):
30 data = file.read() 29 section = match.group(1)
31 for match in re.finditer(r'-+BEGIN CERTIFICATE-+(.*?)-+END CERTIFICATE-+ ', data, re.S): 30 if section == 'CERTIFICATE':
32 certs.append(base64.b64decode(match.group(1))) 31 certificates.append(re.sub(r'\s+', '', match.group(2)))
33 return certs 32 elif section == 'PRIVATE KEY':
33 key = RSA.importKey(match.group(0))
34 if not key:
35 raise Exception('Could not find private key in file')
36
37 return certificates, key
38
34 39
35 def get_checksum(data): 40 def get_checksum(data):
36 return SHA.new(data).digest() 41 return SHA.new(data).digest()
37 42
43
38 def get_hexchecksum(data): 44 def get_hexchecksum(data):
39 return SHA.new(data).hexdigest() 45 return SHA.new(data).hexdigest()
46
40 47
41 def get_signature(key, data): 48 def get_signature(key, data):
42 return PKCS1_v1_5.new(key).sign(SHA.new(data)) 49 return PKCS1_v1_5.new(key).sign(SHA.new(data))
43 50
51
44 def compress_files(filedata, root, offset): 52 def compress_files(filedata, root, offset):
45 files = [] 53 compressed_data = []
46 filedata = sorted(filedata) 54 filedata = sorted(filedata.iteritems())
47 directory_stack = [{'path': '', 'element': root}] 55 directory_stack = [('', root)]
48 file_id = 1 56 file_id = 1
49 for path, data in filedata: 57 for path, data in filedata:
50 # Remove directories that are done 58 # Remove directories that are done
51 while True: 59 while not path.startswith(directory_stack[-1][0]):
52 directory = directory_stack[-1]
53 directory_path = directory['path']
54 if path.startswith(directory_path):
55 break
56 directory_stack.pop() 60 directory_stack.pop()
57 61
58 # Add new directories 62 # Add new directories
63 directory_path = directory_stack[-1][0]
59 relpath = path[len(directory_path):] 64 relpath = path[len(directory_path):]
60 while '/' in relpath: 65 while '/' in relpath:
61 directory_name, relpath = relpath.split('/', 1) 66 name, relpath = relpath.split('/', 1)
62 directory_path += directory_name + '/' 67 directory_path += name + '/'
63 element = ElementTree.SubElement(directory['element'], 'file')
64 directory = { 68 directory = {
65 'path': directory_path, 69 'id': file_id,
66 'element': element, 70 'name': name,
71 'type': 'directory',
72 'mode': '0755',
73 'children': [],
67 } 74 }
68 element.set('id', str(file_id))
69 file_id += 1 75 file_id += 1
70 ElementTree.SubElement(element, 'name').text = directory_name 76 directory_stack[-1][1].append(directory)
71 ElementTree.SubElement(element, 'type').text = 'directory' 77 directory_stack.append((directory_path, directory['children']))
72 ElementTree.SubElement(element, 'mode').text = '0755'
73 directory_stack.append(directory)
74 78
75 # Add the actual file 79 # Add the actual file
76 element = ElementTree.SubElement(directory['element'], 'file') 80 compressed = zlib.compress(data, 9)
77 element.set('id', str(file_id)) 81 file = {
82 'id': file_id,
83 'name': relpath,
84 'type': 'file',
85 'mode': '0644',
86 'checksum_uncompressed': get_hexchecksum(data),
87 'size_uncompressed': len(data),
88 'checksum_compressed': get_hexchecksum(compressed),
89 'size_compressed': len(compressed),
90 'offset': offset,
91 }
78 file_id += 1 92 file_id += 1
79 ElementTree.SubElement(element, 'name').text = relpath 93 offset += len(compressed)
80 ElementTree.SubElement(element, 'type').text = 'file' 94 directory_stack[-1][1].append(file)
81 ElementTree.SubElement(element, 'mode').text = '0644' 95 compressed_data.append(compressed)
96 return compressed_data
82 97
83 datatag = ElementTree.SubElement(element, 'data')
84 ElementTree.SubElement(datatag, 'extracted-checksum', {'style': 'sha1'}) .text = get_hexchecksum(data)
85 ElementTree.SubElement(datatag, 'size').text = str(len(data))
86
87 compressed = zlib.compress(data, 9)
88 ElementTree.SubElement(datatag, 'encoding', {'style': 'application/x-gzi p'})
89 ElementTree.SubElement(datatag, 'archived-checksum', {'style': 'sha1'}). text = get_hexchecksum(compressed)
90 ElementTree.SubElement(datatag, 'offset').text = str(offset)
91 ElementTree.SubElement(datatag, 'length').text = str(len(compressed))
92 offset += len(compressed)
93
94 files.append(compressed)
95 return files
96 98
97 def create(archivepath, contents, keyfile): 99 def create(archivepath, contents, keyfile):
98 key = read_key(keyfile) 100 certificates, key = read_certificates_and_key(keyfile)
99 certs = read_certificates(keyfile) 101 checksum_length = len(get_checksum(''))
102 params = {
103 'certificates': certificates,
100 104
101 root = ElementTree.Element('xar') 105 # Timestamp epoch starts at 2001-01-01T00:00:00.000Z
102 toc = ElementTree.SubElement(root, 'toc') 106 'timestamp_numerical': time.time() - 978307200,
107 'timestamp_iso': time.strftime('%Y-%m-%dT%H:%M:%SZ', time.gmtime()),
103 108
104 creation_time = time.strftime('%Y-%m-%dT%H:%M:%SZ', time.gmtime()) 109 'checksum': {
105 ElementTree.SubElement(toc, 'creation-time').text = creation_time 110 'offset': 0,
111 'size': checksum_length,
112 },
113 'signature': {
114 'offset': checksum_length,
115 'size': len(get_signature(key, '')),
116 },
117 'files': [],
118 }
106 119
107 # Timestamp epoch starts at 2001-01-01T00:00:00.000Z 120 offset = params['signature']['offset'] + params['signature']['size']
108 sign_time = str(time.time() - 978307200) 121 compressed_data = compress_files(contents, params['files'], offset)
109 ElementTree.SubElement(toc, 'signature-creation-time').text = sign_time
110 122
111 offset = 0 123 template = getTemplate('xartoc.xml.tmpl', autoEscape=True)
112 124 toc_uncompressed = template.render(params).encode('utf-8')
113 checksum_size = len(get_checksum(''))
114 checksum = ElementTree.SubElement(toc, 'checksum', {'style': 'sha1'})
115 ElementTree.SubElement(checksum, 'offset').text = str(offset)
116 ElementTree.SubElement(checksum, 'size').text = str(checksum_size)
117 offset += checksum_size
118
119 signature_size = len(get_signature(key, ''))
120 signature = ElementTree.SubElement(toc, 'signature', {'style': 'RSA'})
121 ElementTree.SubElement(signature, 'offset').text = str(offset)
122 ElementTree.SubElement(signature, 'size').text = str(signature_size)
123 offset += signature_size
124
125 keyinfo = ElementTree.SubElement(signature, 'KeyInfo')
126 keyinfo.set('xmlns', 'http://www.w3.org/2000/09/xmldsig#')
127 x509data = ElementTree.SubElement(keyinfo, 'X509Data')
128 for cert in certs:
129 ElementTree.SubElement(x509data, 'X509Certificate').text = base64.b64enc ode(cert)
130
131 files = compress_files(contents.iteritems(), toc, offset)
132
133 toc_uncompressed = ElementTree.tostring(root).encode('utf-8')
134 toc_compressed = zlib.compress(toc_uncompressed, 9) 125 toc_compressed = zlib.compress(toc_uncompressed, 9)
135 126
136 with open(archivepath, 'wb') as file: 127 with open(archivepath, 'wb') as file:
137 # The file starts with a minimalistic header 128 # The file starts with a minimalistic header
138 header = struct.pack('>IHHQQI', XAR_HEADER_MAGIC, XAR_HEADER_SIZE, 129 file.write(XAR_HEADER.pack(XAR_HEADER_MAGIC, XAR_HEADER.size,
139 XAR_VERSION, len(toc_compressed), len(toc_uncompressed), 130 XAR_VERSION, len(toc_compressed),
140 XAR_CKSUM_SHA1) 131 len(toc_uncompressed), XAR_CKSUM_SHA1))
141 file.write(header)
142 132
143 # It's followed up with a compressed XML table of contents 133 # It's followed up with a compressed XML table of contents
144 file.write(toc_compressed) 134 file.write(toc_compressed)
145 135
146 # Now the actual data, all the offsets are in the table of contents 136 # Now the actual data, all the offsets are in the table of contents
147 file.write(get_checksum(toc_compressed)) 137 file.write(get_checksum(toc_compressed))
148 file.write(get_signature(key, toc_compressed)) 138 file.write(get_signature(key, toc_compressed))
149 for compressed in files: 139 for blob in compressed_data:
150 file.write(compressed) 140 file.write(blob)
LEFTRIGHT

Powered by Google App Engine
This is Rietveld