| 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 base64 | 
 |    6 import re | 
 |    7 import struct | 
 |    8 import time | 
 |    9 from xml.etree import ElementTree | 
 |   10 import zlib | 
 |   11  | 
 |   12 from Crypto.Hash import SHA | 
 |   13 from Crypto.PublicKey import RSA | 
 |   14 from Crypto.Signature import PKCS1_v1_5 | 
 |   15  | 
 |   16 XAR_HEADER_MAGIC = 0x78617221 | 
 |   17 XAR_HEADER_SIZE = 28 | 
 |   18 XAR_VERSION = 1 | 
 |   19 XAR_CKSUM_SHA1 = 1 | 
 |   20  | 
 |   21 def read_key(keyfile): | 
 |   22     with open(keyfile, 'r') as file: | 
 |   23         data = file.read() | 
 |   24         data = re.sub(r'(-+END PRIVATE KEY-+).*', r'\1', data, flags=re.S) | 
 |   25         return RSA.importKey(data) | 
 |   26  | 
 |   27 def read_certificates(keyfile): | 
 |   28     certs = [] | 
 |   29     with open(keyfile, 'r') as file: | 
 |   30         data = file.read() | 
 |   31         for match in re.finditer(r'-+BEGIN CERTIFICATE-+(.*?)-+END CERTIFICATE-+
     ', data, re.S): | 
 |   32             certs.append(base64.b64decode(match.group(1))) | 
 |   33     return certs | 
 |   34  | 
 |   35 def get_checksum(data): | 
 |   36     return SHA.new(data).digest() | 
 |   37  | 
 |   38 def get_hexchecksum(data): | 
 |   39     return SHA.new(data).hexdigest() | 
 |   40  | 
 |   41 def get_signature(key, data): | 
 |   42     return PKCS1_v1_5.new(key).sign(SHA.new(data)) | 
 |   43  | 
 |   44 def compress_files(filedata, root, offset): | 
 |   45     files = [] | 
 |   46     filedata = sorted(filedata) | 
 |   47     directory_stack = [{'path': '', 'element': root}] | 
 |   48     file_id = 1 | 
 |   49     for path, data in filedata: | 
 |   50         # Remove directories that are done | 
 |   51         while True: | 
 |   52             directory = directory_stack[-1] | 
 |   53             directory_path = directory['path'] | 
 |   54             if path.startswith(directory_path): | 
 |   55                 break | 
 |   56             directory_stack.pop() | 
 |   57  | 
 |   58         # Add new directories | 
 |   59         relpath = path[len(directory_path):] | 
 |   60         while '/' in relpath: | 
 |   61             directory_name, relpath = relpath.split('/', 1) | 
 |   62             directory_path += directory_name + '/' | 
 |   63             element = ElementTree.SubElement(directory['element'], 'file') | 
 |   64             directory = { | 
 |   65                 'path': directory_path, | 
 |   66                 'element': element, | 
 |   67             } | 
 |   68             element.set('id', str(file_id)) | 
 |   69             file_id += 1 | 
 |   70             ElementTree.SubElement(element, 'name').text = directory_name | 
 |   71             ElementTree.SubElement(element, 'type').text = 'directory' | 
 |   72             ElementTree.SubElement(element, 'mode').text = '0755' | 
 |   73             directory_stack.append(directory) | 
 |   74  | 
 |   75         # Add the actual file | 
 |   76         element = ElementTree.SubElement(directory['element'], 'file') | 
 |   77         element.set('id', str(file_id)) | 
 |   78         file_id += 1 | 
 |   79         ElementTree.SubElement(element, 'name').text = relpath | 
 |   80         ElementTree.SubElement(element, 'type').text = 'file' | 
 |   81         ElementTree.SubElement(element, 'mode').text = '0644' | 
 |   82  | 
 |   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  | 
 |   97 def create(archivepath, contents, keyfile): | 
 |   98     key = read_key(keyfile) | 
 |   99     certs = read_certificates(keyfile) | 
 |  100  | 
 |  101     root = ElementTree.Element('xar') | 
 |  102     toc = ElementTree.SubElement(root, 'toc') | 
 |  103  | 
 |  104     creation_time = time.strftime('%Y-%m-%dT%H:%M:%SZ', time.gmtime()) | 
 |  105     ElementTree.SubElement(toc, 'creation-time').text = creation_time | 
 |  106  | 
 |  107     # Timestamp epoch starts at 2001-01-01T00:00:00.000Z | 
 |  108     sign_time = str(time.time() - 978307200) | 
 |  109     ElementTree.SubElement(toc, 'signature-creation-time').text = sign_time | 
 |  110  | 
 |  111     offset = 0 | 
 |  112  | 
 |  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) | 
 |  135  | 
 |  136     with open(archivepath, 'wb') as file: | 
 |  137         # The file starts with a minimalistic header | 
 |  138         header = struct.pack('>IHHQQI', XAR_HEADER_MAGIC, XAR_HEADER_SIZE, | 
 |  139             XAR_VERSION, len(toc_compressed), len(toc_uncompressed), | 
 |  140             XAR_CKSUM_SHA1) | 
 |  141         file.write(header) | 
 |  142  | 
 |  143         # It's followed up with a compressed XML table of contents | 
 |  144         file.write(toc_compressed) | 
 |  145  | 
 |  146         # Now the actual data, all the offsets are in the table of contents | 
 |  147         file.write(get_checksum(toc_compressed)) | 
 |  148         file.write(get_signature(key, toc_compressed)) | 
 |  149         for compressed in files: | 
 |  150             file.write(compressed) | 
| OLD | NEW |