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