OLD | NEW |
| (Empty) |
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 | |
3 # file, You can obtain one at http://mozilla.org/MPL/2.0/. | |
4 | |
5 import re | |
6 import struct | |
7 import time | |
8 import zlib | |
9 | |
10 from Crypto.Hash import SHA | |
11 from Crypto.PublicKey import RSA | |
12 from Crypto.Signature import PKCS1_v1_5 | |
13 | |
14 from buildtools.packager import getTemplate | |
15 | |
16 XAR_HEADER = struct.Struct('>IHHQQI') | |
17 XAR_HEADER_MAGIC = 0x78617221 | |
18 XAR_VERSION = 1 | |
19 XAR_CKSUM_SHA1 = 1 | |
20 | |
21 | |
22 def read_certificates_and_key(keyfile): | |
23 with open(keyfile, 'r') as file: | |
24 data = file.read() | |
25 | |
26 certificates = [] | |
27 key = None | |
28 for match in re.finditer(r'-+BEGIN (.*?)-+(.*?)-+END \1-+', data, re.S): | |
29 section = match.group(1) | |
30 if section == 'CERTIFICATE': | |
31 certificates.append(re.sub(r'\s+', '', match.group(2))) | |
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 | |
39 | |
40 def get_checksum(data): | |
41 return SHA.new(data).digest() | |
42 | |
43 | |
44 def get_hexchecksum(data): | |
45 return SHA.new(data).hexdigest() | |
46 | |
47 | |
48 def get_signature(key, data): | |
49 return PKCS1_v1_5.new(key).sign(SHA.new(data)) | |
50 | |
51 | |
52 def compress_files(filedata, root, offset): | |
53 compressed_data = [] | |
54 filedata = sorted(filedata.iteritems()) | |
55 directory_stack = [('', root)] | |
56 file_id = 1 | |
57 for path, data in filedata: | |
58 # Remove directories that are done | |
59 while not path.startswith(directory_stack[-1][0]): | |
60 directory_stack.pop() | |
61 | |
62 # Add new directories | |
63 directory_path = directory_stack[-1][0] | |
64 relpath = path[len(directory_path):] | |
65 while '/' in relpath: | |
66 name, relpath = relpath.split('/', 1) | |
67 directory_path += name + '/' | |
68 directory = { | |
69 'id': file_id, | |
70 'name': name, | |
71 'type': 'directory', | |
72 'mode': '0755', | |
73 'children': [], | |
74 } | |
75 file_id += 1 | |
76 directory_stack[-1][1].append(directory) | |
77 directory_stack.append((directory_path, directory['children'])) | |
78 | |
79 # Add the actual file | |
80 compressed = zlib.compress(data, 9) | |
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 } | |
92 file_id += 1 | |
93 offset += len(compressed) | |
94 directory_stack[-1][1].append(file) | |
95 compressed_data.append(compressed) | |
96 return compressed_data | |
97 | |
98 | |
99 def create(archivepath, contents, keyfile): | |
100 certificates, key = read_certificates_and_key(keyfile) | |
101 checksum_length = len(get_checksum('')) | |
102 params = { | |
103 'certificates': certificates, | |
104 | |
105 # Timestamp epoch starts at 2001-01-01T00:00:00.000Z | |
106 'timestamp_numerical': time.time() - 978307200, | |
107 'timestamp_iso': time.strftime('%Y-%m-%dT%H:%M:%SZ', time.gmtime()), | |
108 | |
109 'checksum': { | |
110 'offset': 0, | |
111 'size': checksum_length, | |
112 }, | |
113 'signature': { | |
114 'offset': checksum_length, | |
115 'size': len(get_signature(key, '')), | |
116 }, | |
117 'files': [], | |
118 } | |
119 | |
120 offset = params['signature']['offset'] + params['signature']['size'] | |
121 compressed_data = compress_files(contents, params['files'], offset) | |
122 | |
123 template = getTemplate('xartoc.xml.tmpl', autoEscape=True) | |
124 toc_uncompressed = template.render(params).encode('utf-8') | |
125 toc_compressed = zlib.compress(toc_uncompressed, 9) | |
126 | |
127 with open(archivepath, 'wb') as file: | |
128 # The file starts with a minimalistic header | |
129 file.write(XAR_HEADER.pack(XAR_HEADER_MAGIC, XAR_HEADER.size, | |
130 XAR_VERSION, len(toc_compressed), | |
131 len(toc_uncompressed), XAR_CKSUM_SHA1)) | |
132 | |
133 # It's followed up with a compressed XML table of contents | |
134 file.write(toc_compressed) | |
135 | |
136 # Now the actual data, all the offsets are in the table of contents | |
137 file.write(get_checksum(toc_compressed)) | |
138 file.write(get_signature(key, toc_compressed)) | |
139 for blob in compressed_data: | |
140 file.write(blob) | |
OLD | NEW |