1# Copyright 2012 The Chromium Authors. All rights reserved.
2# Use of this source code is governed by a BSD-style license that can be
3# found in the LICENSE file.
4
5""" Read a CRX file and write out the App ID and the Full Hash of the ID.
6See: http://code.google.com/chrome/extensions/crx.html
7and 'http://stackoverflow.com/questions/'
8  + '1882981/google-chrome-alphanumeric-hashes-to-identify-extensions'
9for docs on the format.
10"""
11
12import base64
13import os
14import hashlib
15import json
16
17EXPECTED_CRX_MAGIC_NUM = 'Cr24'
18EXPECTED_CRX_VERSION = 2
19
20def HexToInt(hex_chars):
21  """ Convert bytes like \xab -> 171 """
22  val = 0
23  for i in xrange(len(hex_chars)):
24    val += pow(256, i) * ord(hex_chars[i])
25  return val
26
27def HexToMPDecimal(hex_chars):
28  """ Convert bytes to an MPDecimal string. Example \x00 -> "aa"
29      This gives us the AppID for a chrome extension.
30  """
31  result = ''
32  base = ord('a')
33  for i in xrange(len(hex_chars)):
34    value = ord(hex_chars[i])
35    dig1 = value / 16
36    dig2 = value % 16
37    result += chr(dig1 + base)
38    result += chr(dig2 + base)
39  return result
40
41def HexTo256(hex_chars):
42  """ Convert bytes to pairs of hex digits. E.g., \x00\x11 -> "{0x00, 0x11}"
43      The format is taylored for copy and paste into C code:
44      const uint8 sha256_hash[] = { ... }; """
45  result = []
46  for i in xrange(len(hex_chars)):
47    value = ord(hex_chars[i])
48    dig1 = value / 16
49    dig2 = value % 16
50    result.append('0x' + hex(dig1)[2:] + hex(dig2)[2:])
51  return '{%s}' % ', '.join(result)
52
53def GetPublicKeyPacked(f):
54  magic_num = f.read(4)
55  if magic_num != EXPECTED_CRX_MAGIC_NUM:
56    raise Exception('Invalid magic number: %s (expecting %s)' %
57                    (magic_num,
58                     EXPECTED_CRX_MAGIC_NUM))
59  version = f.read(4)
60  if not version[0] != EXPECTED_CRX_VERSION:
61    raise Exception('Invalid version number: %s (expecting %s)' %
62                    (version,
63                     EXPECTED_CRX_VERSION))
64  pub_key_len_bytes = HexToInt(f.read(4))
65  f.read(4)
66  return f.read(pub_key_len_bytes)
67
68def GetPublicKeyFromPath(filepath, is_win_path=False):
69  # Normalize the path for windows to have capital drive letters.
70  # We intentionally don't check if sys.platform == 'win32' and just
71  # check if this looks like drive letter so that we can test this
72  # even on posix systems.
73  if (len(filepath) >= 2 and
74      filepath[0].islower() and
75      filepath[1] == ':'):
76    filepath = filepath[0].upper() + filepath[1:]
77
78  # On Windows, filepaths are encoded using UTF-16, little endian byte order,
79  # using "wide characters" that are 16 bits in size. On POSIX systems, the
80  # encoding is generally UTF-8, which has the property of being equivalent to
81  # ASCII when only ASCII characters are in the path.
82  if is_win_path:
83    filepath = filepath.encode('utf-16le')
84
85  return filepath
86
87def GetPublicKeyUnpacked(f, filepath):
88  manifest = json.load(f)
89  if 'key' not in manifest:
90    # Use the path as the public key.
91    # See Extension::GenerateIdForPath in extension.cc
92    return GetPublicKeyFromPath(filepath)
93  else:
94    return base64.standard_b64decode(manifest['key'])
95
96def HasPublicKey(filename):
97  if os.path.isdir(filename):
98    with open(os.path.join(filename, 'manifest.json'), 'rb') as f:
99      manifest = json.load(f)
100      return 'key' in manifest
101  return False
102
103def GetPublicKey(filename, from_file_path, is_win_path=False):
104  if from_file_path:
105    return GetPublicKeyFromPath(
106        filename, is_win_path=is_win_path)
107
108  pub_key = ''
109  if os.path.isdir(filename):
110    # Assume it's an unpacked extension
111    f = open(os.path.join(filename, 'manifest.json'), 'rb')
112    pub_key = GetPublicKeyUnpacked(f, filename)
113    f.close()
114  else:
115    # Assume it's a packed extension.
116    f = open(filename, 'rb')
117    pub_key = GetPublicKeyPacked(f)
118    f.close()
119  return pub_key
120
121def GetCRXHash(filename, from_file_path=False, is_win_path=False):
122  pub_key = GetPublicKey(filename, from_file_path, is_win_path=is_win_path)
123  pub_key_hash = hashlib.sha256(pub_key).digest()
124  return HexTo256(pub_key_hash)
125
126def GetCRXAppID(filename, from_file_path=False, is_win_path=False):
127  pub_key = GetPublicKey(filename, from_file_path, is_win_path=is_win_path)
128  pub_key_hash = hashlib.sha256(pub_key).digest()
129  # AppID is the MPDecimal of only the first 128 bits of the hash.
130  return HexToMPDecimal(pub_key_hash[:128/8])
131