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