cros: Update SAML flow.
[chromium-blink-merge.git] / tools / crx_id / crx_id.py
blobdc30b736dc6916af6c9f392cd4bd57e484d92d12
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.
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 """
13 import base64
14 import os
15 import sys
16 import hashlib
18 try:
19 import json
20 except Exception:
21 import simplejson as json
23 EXPECTED_CRX_MAGIC_NUM = 'Cr24'
24 EXPECTED_CRX_VERSION = 2
26 def usage(argv):
27 print "%s: crx_file" % argv[0]
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
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
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)
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
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:]
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')
95 return filepath
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'])
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
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)
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
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)
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])
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])
150 if __name__ == '__main__':
151 sys.exit(main(sys.argv))