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